| 1 | #!/bin/bash |
|---|
| 2 | # |
|---|
| 3 | # fetch-sanesecurity-sigs |
|---|
| 4 | # by Malcolm Scott, Retrosnub Internet Services |
|---|
| 5 | # <malcolm at retrosnub dot co dot uk> |
|---|
| 6 | # |
|---|
| 7 | # http://www.retrosnub.co.uk/sanesecurity |
|---|
| 8 | # svn://svn.sanesecurity.retrosnub.co.uk/ |
|---|
| 9 | # |
|---|
| 10 | # $Revision$ |
|---|
| 11 | # $Date$ |
|---|
| 12 | # |
|---|
| 13 | # ----------------------------------------------------------------------------- |
|---|
| 14 | # Copyright (C) 2009 Malcolm Scott |
|---|
| 15 | # |
|---|
| 16 | # This program is free software; you can redistribute it and/or modify it under |
|---|
| 17 | # the terms of the GNU General Public License version 2 as published by the |
|---|
| 18 | # Free Software Foundation. |
|---|
| 19 | # |
|---|
| 20 | # This program is distributed in the hope that it will be useful, but WITHOUT |
|---|
| 21 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|---|
| 22 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
|---|
| 23 | # |
|---|
| 24 | # You should have received a copy of the GNU General Public License along with |
|---|
| 25 | # this program; if not, write to the Free Software Foundation, Inc., 59 Temple |
|---|
| 26 | # Place, Suite 330, Boston, MA 02111-1307 USA |
|---|
| 27 | # ----------------------------------------------------------------------------- |
|---|
| 28 | # |
|---|
| 29 | # INSTALLATION AND USAGE: |
|---|
| 30 | # |
|---|
| 31 | # * Ensure that you have wget, gpg and rsync installed (in addition to a |
|---|
| 32 | # standard set of UNIX tools). |
|---|
| 33 | # * Configure this script by editing the configuration variables below. |
|---|
| 34 | # * Set up cron to run this script periodically, at a randomly-selected time |
|---|
| 35 | # (i.e. not just on the hour unless unavoidable), not more often than once |
|---|
| 36 | # per hour. The script should be run either as root or as the user clamd |
|---|
| 37 | # runs as. For example, add the following line to /etc/crontab or a file |
|---|
| 38 | # in /etc/cron.d: |
|---|
| 39 | # 42 * * * * root /path/to/fetch-sanesecurity-sigs >/dev/null |
|---|
| 40 | # (Redirection of the output to /dev/null is recommended when running from |
|---|
| 41 | # cron as the script is verbose in normal operation. This will not hide |
|---|
| 42 | # errors, however.) |
|---|
| 43 | # |
|---|
| 44 | # Note that this script is tailored to the Sanesecurity setup (as of January |
|---|
| 45 | # 2009). It should replace scripts using the pre-2009 setup (HTTP). It is |
|---|
| 46 | # not intended to download non-Sanesecurity signatures; use another script |
|---|
| 47 | # for that purpose. |
|---|
| 48 | |
|---|
| 49 | |
|---|
| 50 | ### Configuration ### |
|---|
| 51 | |
|---|
| 52 | # ClamAV database location |
|---|
| 53 | clamd_dbdir="/var/lib/clamav" |
|---|
| 54 | |
|---|
| 55 | # ClamAV daemon process ID file |
|---|
| 56 | # (If this is commented out, the daemon will not be reloaded automatically) |
|---|
| 57 | clamd_pidfile="/var/run/clamav/clamd.pid" |
|---|
| 58 | |
|---|
| 59 | # Directory in which this script will keep its persistent data |
|---|
| 60 | data_dir="/var/lib/sanesecurity" |
|---|
| 61 | |
|---|
| 62 | # Directory in which this script will keep its cache |
|---|
| 63 | # (This should not be the same as data_dir. Any unexpected files in this |
|---|
| 64 | # directory will be deleted!) |
|---|
| 65 | cache_dir="/var/cache/sanesecurity" |
|---|
| 66 | |
|---|
| 67 | # Mirror to use, in a form accepted by rsync |
|---|
| 68 | # (Leave set to the round robin address unless you know what you are doing: |
|---|
| 69 | # forcing the use of one particular mirror will cause excess load for that |
|---|
| 70 | # mirror) |
|---|
| 71 | mirror="rsync://rsync.sanesecurity.net/sanesecurity" |
|---|
| 72 | |
|---|
| 73 | # Extra options for rsync |
|---|
| 74 | # The default, --contimeout=30, makes for faster recovery if a mirror is |
|---|
| 75 | # blocking your connection (e.g. if you connect too frequently). However |
|---|
| 76 | # this is not available in older versions of rsync so you may need to |
|---|
| 77 | # remove it. |
|---|
| 78 | # Additionally, if you have a slow link you may want to add -z here to |
|---|
| 79 | # enable compression. |
|---|
| 80 | rsync_extra_opts="--contimeout=30" |
|---|
| 81 | |
|---|
| 82 | # Minimum interval between updates, in minutes |
|---|
| 83 | # (Note that the download servers may be configured to drop connections made |
|---|
| 84 | # at too great a rate) |
|---|
| 85 | min_interval="30" |
|---|
| 86 | |
|---|
| 87 | # Randomly sleep before downloading if running noninteractively? |
|---|
| 88 | # (Please leave this enabled, as it reduces peak load on the mirrors) |
|---|
| 89 | random_sleep=1 |
|---|
| 90 | |
|---|
| 91 | # URL of the Sanesecurity GnuPG public key |
|---|
| 92 | gpg_key_url="http://www.sanesecurity.net/publickey.gpg" |
|---|
| 93 | |
|---|
| 94 | # Location of GnuPG home directory |
|---|
| 95 | # (If you change this, be sure that you understand the security implications: |
|---|
| 96 | # signatures by *any* key in your public keyring will be accepted) |
|---|
| 97 | gpg_homedir="$data_dir/gnupg" |
|---|
| 98 | |
|---|
| 99 | # Extra options for GnuPG, if required |
|---|
| 100 | gpg_extra_opts="" |
|---|
| 101 | |
|---|
| 102 | # Exclude logical signatures (*.ldb)? |
|---|
| 103 | # These are not supported by versions of ClamAV prior to 0.94. |
|---|
| 104 | # If you use an old version of ClamAV, you should enable this option. |
|---|
| 105 | #exclude_ldb=1 |
|---|
| 106 | |
|---|
| 107 | ### End of configuration ### |
|---|
| 108 | |
|---|
| 109 | |
|---|
| 110 | umask 0022 |
|---|
| 111 | |
|---|
| 112 | # Check the configuration looks sane |
|---|
| 113 | if [ ! -d "$clamd_dbdir" ] |
|---|
| 114 | then |
|---|
| 115 | echo "clamd_dbdir ($clamd_dbdir) does not exist; aborting" >&2 |
|---|
| 116 | echo "(check your configuration)" >&2 |
|---|
| 117 | exit 1 |
|---|
| 118 | fi |
|---|
| 119 | |
|---|
| 120 | mkdir -p "$data_dir" "$cache_dir" |
|---|
| 121 | |
|---|
| 122 | # Set up GnuPG, if necessary |
|---|
| 123 | if [ ! -d "$gpg_homedir" ] |
|---|
| 124 | then |
|---|
| 125 | echo "GnuPG homedir is nonexistant; initialising" >&2 |
|---|
| 126 | echo "(This should only occur once)" >&2 |
|---|
| 127 | mkdir -p "$gpg_homedir" |
|---|
| 128 | chmod 0700 "$gpg_homedir" |
|---|
| 129 | gpg_tmp="$(mktemp -t fetch-sanesecurity-sigs.XXXXXXXXXX)" |
|---|
| 130 | if ! wget -O "$gpg_tmp" "$gpg_key_url" |
|---|
| 131 | then |
|---|
| 132 | echo "ERROR: could not fetch GnuPG public key; aborting" >&2 |
|---|
| 133 | rm -f "$gpg_tmp" |
|---|
| 134 | exit 4 |
|---|
| 135 | fi |
|---|
| 136 | if ! gpg --homedir "$gpg_homedir" $gpg_extra_opts --import "$gpg_tmp" |
|---|
| 137 | then |
|---|
| 138 | echo "ERROR: could not import GnuPG public key; aborting" >&2 |
|---|
| 139 | rm -f "$gpg_tmp" |
|---|
| 140 | exit 4 |
|---|
| 141 | fi |
|---|
| 142 | rm -f "$gpg_tmp" |
|---|
| 143 | fi |
|---|
| 144 | |
|---|
| 145 | # This appears to be the most portable way to find the current timestamp |
|---|
| 146 | # (suggestions on how to avoid calling perl are very welcome) |
|---|
| 147 | time_now="$(perl -le print+time)" |
|---|
| 148 | if [ ! "$time_now" -gt 1 ] |
|---|
| 149 | then |
|---|
| 150 | echo "Could not find current timestamp -- is perl installed?" >&2 |
|---|
| 151 | exit 5 |
|---|
| 152 | fi |
|---|
| 153 | |
|---|
| 154 | # Check that min_interval seconds have elapsed since last run |
|---|
| 155 | if [ -s "$data_dir/update-stamp" ] |
|---|
| 156 | then |
|---|
| 157 | time_then="$(cat "$data_dir/update-stamp")" |
|---|
| 158 | minutes_elapsed=$(( (time_now - time_then) / 60 )) |
|---|
| 159 | if [ "$minutes_elapsed" -lt "$min_interval" ] |
|---|
| 160 | then |
|---|
| 161 | echo "Must wait at least $min_interval minutes between updates; aborting" >&2 |
|---|
| 162 | exit 2 |
|---|
| 163 | fi |
|---|
| 164 | fi |
|---|
| 165 | |
|---|
| 166 | # Random sleep (see comment in configuration section) |
|---|
| 167 | if [ "$random_sleep" = "1" ] |
|---|
| 168 | then |
|---|
| 169 | # Are we running noninteractively, i.e. without a tty on stdin? |
|---|
| 170 | if ! tty -s |
|---|
| 171 | then |
|---|
| 172 | # $RANDOM is between 0 and 32767. |
|---|
| 173 | # Use this to generate a time between 30 and 32767/200+30 = 193 seconds. |
|---|
| 174 | sleep_time=$((RANDOM/200+30)) |
|---|
| 175 | echo "Sleeping for $sleep_time seconds." |
|---|
| 176 | sleep $sleep_time |
|---|
| 177 | fi |
|---|
| 178 | fi |
|---|
| 179 | |
|---|
| 180 | # Update the cache |
|---|
| 181 | if [ "$exclude_ldb" = "1" ] |
|---|
| 182 | then |
|---|
| 183 | rsync_extra_opts="$rsync_extra_opts --exclude=*.ldb" |
|---|
| 184 | rm -f "$cache_dir"/*.ldb |
|---|
| 185 | fi |
|---|
| 186 | if ! rsync --progress --delete -rt $rsync_extra_opts "$mirror/." "$cache_dir/" |
|---|
| 187 | then |
|---|
| 188 | echo "rsync failed; aborting." >&2 |
|---|
| 189 | exit 3 |
|---|
| 190 | fi |
|---|
| 191 | echo "$time_now" > "$data_dir/update-stamp" |
|---|
| 192 | chmod 0644 "$cache_dir"/* |
|---|
| 193 | echo |
|---|
| 194 | |
|---|
| 195 | # Iterate through the databases in the cache |
|---|
| 196 | installed=0 |
|---|
| 197 | for db in "$cache_dir"/*.?db "$cache_dir"/*.ftm |
|---|
| 198 | do |
|---|
| 199 | db_name=$(basename "$db") |
|---|
| 200 | db_live="$clamd_dbdir/sanesecurity-$db_name" |
|---|
| 201 | |
|---|
| 202 | # Only pay any attention to this database if it's newer than the installed version |
|---|
| 203 | if [ -e "$db_live" -a ! "$db" -nt "$db_live" ] |
|---|
| 204 | then |
|---|
| 205 | echo "$db_name is already up-to-date; skipping" |
|---|
| 206 | continue |
|---|
| 207 | fi |
|---|
| 208 | |
|---|
| 209 | # Zero-length databases have no value and confuse the test below |
|---|
| 210 | if [ ! -s "$db" ] |
|---|
| 211 | then |
|---|
| 212 | echo "$db_name is zero-length; discarding" |
|---|
| 213 | continue |
|---|
| 214 | fi |
|---|
| 215 | |
|---|
| 216 | # Check that the GnuPG signature is present and correct |
|---|
| 217 | if ! gpg_out=$(gpg --homedir "$gpg_homedir" $gpg_extra_opts --verify "$db.sig" "$db" 2>&1) |
|---|
| 218 | then |
|---|
| 219 | echo "SECURITY ERROR: $db_name has a bad GnuPG signature; discarding:" >&2 |
|---|
| 220 | echo "$gpg_out" >&2 |
|---|
| 221 | continue |
|---|
| 222 | fi |
|---|
| 223 | |
|---|
| 224 | # Test the database by asking ClamAV to check something with it |
|---|
| 225 | if ! clamscan --quiet --tempdir="${TMPDIR:-/tmp}" --database="$db" - < /dev/null |
|---|
| 226 | then |
|---|
| 227 | echo "ERROR: $db_name fails a simple test; discarding" >&2 |
|---|
| 228 | continue |
|---|
| 229 | fi |
|---|
| 230 | |
|---|
| 231 | # Now we can actually install this database |
|---|
| 232 | echo "Installing $db_name into $db_live" |
|---|
| 233 | if rsync -p "$db" "$db_live" |
|---|
| 234 | then |
|---|
| 235 | installed=$((installed+1)) |
|---|
| 236 | |
|---|
| 237 | # Clean up stuff left behind by old scripts |
|---|
| 238 | rm -f "$clamd_dbdir/$db_name"* |
|---|
| 239 | fi |
|---|
| 240 | done |
|---|
| 241 | |
|---|
| 242 | # Finished; display summary and perhaps reload clamd |
|---|
| 243 | echo |
|---|
| 244 | if [ "$installed" -gt 0 ] |
|---|
| 245 | then |
|---|
| 246 | if [ "$installed" -eq 1 ] |
|---|
| 247 | then |
|---|
| 248 | s="" |
|---|
| 249 | else |
|---|
| 250 | s="s" |
|---|
| 251 | fi |
|---|
| 252 | echo "Installed $installed database$s" |
|---|
| 253 | |
|---|
| 254 | # Is clamd running? |
|---|
| 255 | if [ "$clamd_pidfile" -a -f "$clamd_pidfile" ] |
|---|
| 256 | then |
|---|
| 257 | echo "Reloading ClamAV daemon" |
|---|
| 258 | clamdscan --reload |
|---|
| 259 | fi |
|---|
| 260 | else |
|---|
| 261 | echo "No databases installed." |
|---|
| 262 | fi |
|---|
| 263 | |
|---|
| 264 | exit 0 |
|---|