ec3ecdf492087eeadd98ea4ea24d0679f962f94a
[clamav-cn.git] / clamav-sanesecurity
1 #!/bin/bash
2 #
3 # $Id: update_sanesecurity.sh 706 2008-09-15 20:27:11Z mendel $
4 #
5 # A Modified version of the update script originally written by
6 # Bill Landry
7 #
8 # Modified by Rick Cooper: Contact sanescript@dwford.com
9 #
10 # Modified by Norbert Buchmuller <norbi@nix.hu>
11 #
12 # TODO:
13 #       * split off changelog to a separate file
14 #       * split off configuration to a separate file
15 #       * support optional gzipping (if the downloaded file ends in .gz|.bz2, unzip it)
16 #       * support for protocol (rsync://|http://|https://|ftp://) auto-detection (and call the appropriate download method)
17 #       * support for more flexible download URLs (a list of them eg.)
18 #       * support for external configuration file
19 #       * support for additional DBs (securiteinfo.com, www.malware.com.br)
20 #
21 # Last updated Sep 15, 2008
22 #       FIX 09/15/2008
23 #       Looks for 'main.cld' in $clam_db_dir as new versions of ClamAV use
24 #               '.cld' extension for virus definition files.
25 #
26 #       FIX 04/26/2008
27 #       Changed '--debug' to mean '--syslog-loglevel=none --stderr-loglevel=debug'.
28 #       Added diagnostic message when tty is detected (and so random sleep is disabled).
29 #
30 #       FIX 12/21/2007
31 #       Fixed a grave bug: previously it failed to detect if the newly downloaded
32 #               database was corrupt.
33 #       Detects if the user runs the script from a terminal, and disables
34 #               the random sleep if it is so.
35 #
36 #       FIX 09/26/2007
37 #       Fixed a bug: now it passes the log levels to the unprivileged child.
38 #       Fixed a bug: now the unprivileged child does not attempt to reload ClamAV db.
39 #       Prints usage message if asked for.
40 #
41 #       FIX 09/21/2007
42 #       Added SELinux support.
43 #               Thanks Andrew Colin Kissa <kissaa@sentech.co.za>.
44 #       Added support to run the script as root (the superuser privileges are only used
45 #               when changing the owner:group and security context of the signature files,
46 #               and when reloading clamd).
47 #
48 #       FIX 09/20/2007
49 #       Fixed a bug introduced by the 08/28/2007 change: the "SPAM.ndb" file was saved
50 #               (incorrectly) with the name "SPAM.hdb" and thus ClamAV refused to load it.
51 #               Thanks Keith Brazington <keith@quetz.co.uk>.
52 #
53 #       FIX 08/28/2007
54 #       Refactored logging to avoid code/text duplication.
55 #       Refactored downloading to avoid code duplication.
56 #       Split up the main function into smaller ones.
57 #       Should be started as the clamav user, not root to avoid
58 #               security implications.
59 #       Uses proper temporary dir creation method (mktemp(1)) to avoid
60 #               security implications.
61 #       It is assured that a non-zero exit status is used when exiting because of an error.
62 #       Uses 'mail' syslog facility.
63 #       Different syslog log priorities are used for messages with different severity.
64 #       Diagnostic messages (incl. debug messages) go to stderr instead of stdout.
65 #       More careful quoting to allow spaces or other unexpected chars in variables.
66 #       Checking carefully for the exit status of all the important commands.
67 #       Rsync downloads the new signature file to a temporary directory
68 #               and only installs it after it is verified that ClamAV accepts
69 #               the signature file. (Note: This requires a relatively new version of Rsync
70 #               because versions older than cca. 2.6.9 unconditionally download the file, even
71 #               if it did not change.)
72 #       Uses '-p' option of 'cp' to preserve modification times.
73 #       No separate log files for each command, instead their output is logged if
74 #               they return a non-zero exit status.
75 #       PATH is appended to, not overwritten.
76 #       Use 'type -P' instead of 'which'.
77 #       Fixed a few typos.
78 #
79 #       FIX 08/13/2007
80 #       Using new SaneSecurity URLs
81 #
82 #       FIX 06/30/2007
83 #       Removed the -h (human readable) option from the rsync command line
84 #               as this was for the help option in older versions of rsync, and
85 #               it's not worth maintaining two versions of the command based on
86 #               rsync version. Thanks for the bug report Chris
87 #       Changed the SCAM_SIGS_URL variable to point to the proper URL. The old one
88 #       worked but this one is the correct/desired URL Thanks Steve Basford.
89 #
90 #       FIX 06/27/2007
91 #       Fixed incorrect check on CURL return code in phish.db section
92 #               causing it to consider a success as a failure. Thanks
93 #               Leonardo Rodrigues Magalhães
94 #
95 #       FIX 05/16/2007
96 #       Fixed a bug where downloading phish.ndb.gz for the first time results
97 #               in an error and the downloaded file removed (Thanks Gary V)
98 #
99 #       NEW 05/15/2007
100 #       Now have option to log to system logger (notify)
101 #               If you do not wish to use this logging function look below for
102 #               SYSLOG_ON=1 and set to SYSLOG_ON=0. This also will not function
103 #               if logger is not installed on your system for some reason
104 #
105 #       Now logs each download stats regardless of debug status, but only outputs
106 #               to console if in debugging mode, or error and doesn't output
107 #               to system logger.
108 #                              (as suggested by Gary V)
109 #       Changed the clamdb grep to use extended pattern matching as suggested
110 #               by Gary V
111 #       Altered the rsync portions to use --stats instead of --progress and
112 #               look for number of files transfered for confirmation of an update
113 #
114 #       NEW 05/08/2007
115 #       Updated the MSRBL-* files to update vi rsync (tested version 2.6.9)
116 #               the current db(s) will be saved before the update and if there is a
117 #               problem with the download or clam db tests the old version is
118 #               moved back into place, an error message is produced and the
119 #               corrupt file is moved to filename.bad for the operator to look into
120 #       Changed the detection of the clam db directory it's now very fast
121 #               and will accommodate trailing slash (ie. /usr/local/share/clamav/) and
122 #               will also check for main.cvd as well as the *.inc directories since
123 #               apparently it's possible to have an install with only the main.cvd,
124 #               at least temporarily
125 #
126 #       NEW 05/05/2007
127 #       Fixed a potential problem with the downloaded file size being zero
128 #       Now test a small txt file using the downloaded sig file, if clam doesn't
129 #               like it we don't move it or use it. Throw an error to the operator
130 #       Fixed a possible issue with the log size being very large if the site is
131 #               busy, now only return the last line from the transmission progress
132 #       Changed the operation to find the clam database dir to checking for
133 #               the standard /usr/local/share/clamav location first, and if not there
134 #               ask clamscan. (save a few seconds)
135 #
136 #
137
138 # Make sure the path to your ClamAV binaries is in here, it should
139 # cover all normal installations
140 export PATH="$PATH":/bin:/usr/bin:/usr/local/bin
141
142 # The file names and URLs of the scam and phish signature files from SaneSecurity
143 SCAM_SIGS="scam.ndb"
144 SCAM_SIGS_URL="http://www.sanesecurity.com/clamav/scamsigs/scam.ndb.gz"
145 PHISH_SIGS="phish.ndb"
146 PHISH_SIGS_URL="http://www.sanesecurity.com/clamav/phishsigs/phish.ndb.gz"
147
148 # The URLs of the spam and image-spam signature files from MSRBL
149 MSRBL_SPAM_SIGS="MSRBL-SPAM.ndb"
150 MSRBL_SPAM_SIGS_URL="rsync://rsync.mirror.msrbl.com/msrbl/MSRBL-SPAM.ndb"
151 MSRBL_IMAGE_SIGS="MSRBL-Images.hdb"
152 MSRBL_IMAGE_SIGS_URL="rsync://rsync.mirror.msrbl.com/msrbl/MSRBL-Images.hdb"
153
154 # Log messages with this or greater severity to syslog
155 syslog_loglevel=error
156
157 # Log messages with this or greater severity to standard error
158 stderr_loglevel=none
159
160 # Use this syslog facility
161 syslog_facility=mail
162
163 # The script will sleep for a random amount of time before starting the
164 # actual update. This evens out the load on the update servers when people
165 # have a tendency to set cron jobs on the hour, half hour or quarter hour.
166 # The extra padding keeps the servers from being hammered all at once.
167 # These are the minimum and maximum sleep times in seconds.
168 min_sleep_time=30
169 max_sleep_time=600
170
171 # Should the script reload the clamd service
172 # (Should not be necessary if you have "SelfCheck" enabled in clamd.conf
173 # and "NotifyClamd" enabled in freshclam.conf.)
174 reload_clamd=0
175
176 # If the script is run as root, change the owner and group of
177 # the signature files to this user and group. (The username
178 # and group name should be separated by a colon.)
179 sigfile_owner_and_group=clamav:clamav
180
181 # If the script is run as root, perform downloading, checking and
182 # installation of the signature files as this user. (The SELinux
183 # security context fixing and the clamd reload need superuser
184 # privileges, so these will be performed as root.)
185 # Note: This user must be able to read and write signature files
186 # in ClamAV db dir.
187 unprivileged_user=${sigfile_owner_and_group%:*}
188
189 # Whether to preserve the temporary directory (for debugging purposes)
190 # on exit instead of deleting it (the default)
191 keep_temp_dir=0
192
193 ####################################################################
194 # No user tunable variables below
195 ####################################################################
196
197 ####################################################################
198 # Logging functions
199 #
200
201 # Return the numeric constant associated with the given
202 # severity name.
203 #
204 # Usage: numeric_loglevel=`numeric_log_severity $loglevel_name`
205 #
206 numeric_log_severity()
207 {
208         local name="$1"
209
210         # The severity names (same as in syslog)
211         local -a severity_names=(
212                 debug,deb,dbg
213                 info,inf
214                 notice
215                 warning,warn,wrn
216                 err,error
217                 crit,critical
218                 alert
219                 emerg,emergency,panic
220                 none,off
221         )
222
223         local i=0
224         local numeric_level
225         while [ -n "${severity_names[i]}" ]; do
226                 for levelname in ${severity_names[i]//,/ }; do
227                         if [ "$name" == "$levelname" ]; then
228                                 numeric_level=$i
229                                 break 2
230                         fi
231                 done
232                 let i++
233         done
234         if [ -z "$numeric_level" ]; then
235                 numeric_level=`numeric_log_severity debug`
236         fi
237
238         echo $numeric_level
239 }
240
241 # Log the given message with the given priority.
242 # Logging includes printing to stderr and sending the message to
243 # syslog, depending on the debug level and syslog loglevel settings.
244 #
245 # Usage: log level message [message_continuation [...]]
246 #
247 log()
248 {
249         local level="$1"
250         local -a message_parts='("${@:2}")'
251
252         local message=`echo -e "${message_parts[@]}"`
253
254         if [ $(numeric_log_severity $level) -ge $(numeric_log_severity $stderr_loglevel) ]; then
255                 echo "$program_invocation_short_name: [$level] $message" >&2
256         fi
257         if [ $(numeric_log_severity $level) -ge $(numeric_log_severity $syslog_loglevel) ]; then
258                 if [ -n "$logger" ]; then
259                         "$logger" -p ${syslog_facility}.${level} -i -t "$program_invocation_short_name" -- "${message//$'\n'/\\n}"
260                 fi
261         fi
262 }
263
264 ####################################################################
265 # Utility functions
266 #
267
268 # Run the command silently. If the command exits with a
269 # non-zero exit status, log an error message including the command run,
270 # the exit status and and the collected output, otherwise swallow the output.
271 #
272 # Usage: run_cmd "$cmd" ["$arg1" [...]]
273 #
274 run_cmd()
275 {
276         local -a cmd_and_args='("${@}")'
277
278         local output    # must be a separate command, as 'local' always returns 0 exit status
279         output=`"${cmd_and_args[@]}" 2>&1`
280         local exit_status=$?
281         if [ $exit_status -ne 0 ]; then
282                 log err "Error executing command <<<${cmd_and_args[*]}>>>, exit status: $exit_status, output: <<<$output>>>"
283         fi
284
285         return $exit_status
286 }
287
288 # Push one or more elements to the end of the array.
289 #
290 # Usage: push array_name "$elem1" [...]
291 #
292 push()
293 {
294         local -a array="$1" elems='("${@:2}")'
295
296         local len=`eval echo \\\${#$array[@]}`
297         for elem in "${elems[@]}"; do
298                 eval "$array[$len]=$elem"
299                 let len++
300         done
301 }
302
303 ####################################################################
304 # Signature file download/test/installation functions
305 #
306
307 # Check if ClamAV accepts the signature file,
308 # and moves it to the database dir if so.
309 # Exit status:
310 #       0   - sigfile was updated successfully
311 #       1   - sigfile was up-to-date or an error occurred
312 #
313 # Usage: check_and_install_sigfile "$file_basename"
314 #
315 check_and_install_sigfile()
316 {
317         local filename="$1"
318
319         local sigfile="$clam_db_dir/$filename"
320         local new_sigfile="$tmp_dir/$filename"
321
322         local sigfile_updated=0
323
324         if [ -s "$new_sigfile" ]; then
325                 # First we do a quick test of the downloaded file. If ClamAV doesn't
326                 # like it we won't use it and issue an error to the operator
327                 # renaming the file so they can inspect it themselves.
328                 run_cmd "$clamscan" --quiet -d "$new_sigfile" "$test_file"
329                 local exit_status=$?
330                 if [ $exit_status -eq 0 ]; then
331                         if [ -s "$sigfile" ]; then
332                                 run_cmd cp -pf "$sigfile" "${sigfile}.bak"
333                         fi
334                         run_cmd mv -f "$new_sigfile" "$sigfile"
335                         exit_status=$?
336                         if [ $exit_status -eq 0 ]; then
337                                 sigfile_updated=1
338                         else
339                                 log err "Cannot move '$new_sigfile' to '$sigfile', 'mv' exit status: $exit_status"
340                         fi
341                 else
342                         log err "ClamAV had a problem using '$new_sigfile' (exit status: $exit_status)."
343                         log err "We will NOT install '$new_sigfile' into the database directory."
344                         run_cmd mv -f "$new_sigfile" "${sigfile}.bad"
345                         exit_status=$?
346                         if [ $exit_status -eq 0 ]; then
347                                 log err "Preserving the corrupt file as '${sigfile}.bad' for you to check."
348                         else
349                                 log err "Cannot move the corrupt file from '$new_sigfile' to '${sigfile}.bad', 'mv' exit status: $exit_status."
350                         fi
351                 fi
352         elif [ -e "$new_sigfile" ]; then
353                 log warning "'$new_sigfile' was zero bytes and will not be used!"
354         fi
355
356         local ret_val=1
357         if [ $sigfile_updated -ne 0 ]; then
358                 log info "'$sigfile' was updated"
359                 ret_val=0
360         else
361                 log info "'$sigfile' was NOT updated"
362                 ret_val=1
363         fi
364
365         return $ret_val
366 }
367
368 # Update/download a gzip-compressed signature file using CURL,
369 # uncompress it, check if ClamAV accepts it, and install it in
370 # the database directory.
371 # Exit status:
372 #       0   - sigfile was updated successfully
373 #       1   - sigfile was up-to-date or an error occurred
374 #
375 # Usage: update_sigfile_with_curl "$url" "$file_basename"
376 #
377 update_sigfile_with_curl()
378 {
379         local url="$1" filename="$2"
380
381         if [ -z "$url" ]; then
382                 log info "Skipping '$filename' because no URL is configured for it"
383                 return 1
384         fi
385
386         local sigfile_gz="$clam_db_dir/${filename}.gz"
387         local new_sigfile_gz="$tmp_dir/${filename}.gz"
388         local sigfile="$clam_db_dir/$filename"
389         local new_sigfile="$tmp_dir/$filename"
390
391         local sigfile_gz_updated=0
392
393         declare -a curl_additional_flags
394
395         # If something happend to the sig file, or this is the first time
396         # this script has been run then we can't do the date test on the
397         # current file so we just grab what ever is current on the site
398         if [ -s "$sigfile_gz" ]; then
399                 log debug "Checking for newer version of '$sigfile_gz'"
400                 push curl_additional_flags "-z" "$sigfile_gz"
401         else
402                 log debug "'$sigfile_gz' does not exist, so doing initial download"
403         fi
404
405         if [ $stderr_loglevel != debug ]; then
406                 push curl_additional_flags "-s"
407         fi
408
409         run_cmd "$curl" -R "${curl_additional_flags[@]}" -o "$new_sigfile_gz" \
410                 -f -v --referer ";auto" --location "$url"
411         local exit_status=$?
412         if [ $exit_status -eq 0 -o $exit_status -eq 22 ]; then
413                 # If we don't have the download or it's zero bytes
414                 # something went wrong and we did not get an update.
415                 if [ ! -s "$new_sigfile_gz" ]; then
416                         if [ -e "$new_sigfile_gz" ]; then
417                                 rm -f "$new_sigfile_gz"
418                                 log warning "'$new_sigfile_gz' was zero bytes and will not be used!"
419                         fi
420                         if [ $exit_status -eq 22 ]; then
421                                 log warning "CURL returned an error code 22 which results from a HTTP error 4xx"
422                                 log warning "This might be caused by the file not being updated (HTTP 412) but"
423                                 log warning "it could be something else."
424                         fi
425                 fi
426         else
427                 log err "CURL had a problem getting '$new_sigfile_gz' from '$url', exit status: $exit_status"
428                 rm -f "$new_sigfile_gz"
429         fi
430
431         if [ -s "$new_sigfile_gz" ]; then
432                 "$gunzip" -cdf "$new_sigfile_gz" > "$new_sigfile"
433                 exit_status=$?
434                 if [ $exit_status -eq 0 ]; then
435                         run_cmd mv -f "$new_sigfile_gz" "$sigfile_gz"
436                         exit_status=$?
437                         if [ $exit_status -eq 0 ]; then
438                                 sigfile_gz_updated=1
439                         else
440                                 log err "Cannot move '$new_sigfile_gz' to '$sigfile_gz', 'mv' exit status: $exit_status"
441                         fi
442                 else
443                         rm -f "$new_sigfile_gz"
444                         log err "Cannot uncompress '$new_sigfile_gz', 'gunzip' exit status: $exit_status"
445                 fi
446         fi
447
448         if [ $sigfile_gz_updated -ne 0 ]; then
449                 log info "'$sigfile_gz' was updated"
450         else
451                 log info "'$sigfile_gz' was NOT updated"
452         fi
453
454         check_and_install_sigfile "$filename"
455 }
456
457 # Update/download the signature file using Rsync,
458 # check if ClamAV accepts it, and and installs it in
459 # the database directory.
460 # Exit status:
461 #       0   - sigfile was updated successfully
462 #       1   - sigfile was up-to-date or an error occurred
463 #
464 # Usage: update_sigfile_with_curl "$url" "$file_basename"
465 #
466 update_sigfile_with_rsync()
467 {
468         local url="$1" filename="$2"
469
470         if [ -z "$url" ]; then
471                 log info "Skipping '$filename' because no URL is configured for it"
472                 return 1
473         fi
474
475         local sigfile="$clam_db_dir/$filename"
476         local new_sigfile="$tmp_dir/$filename"
477
478         if [ -s "$sigfile" ]; then
479                 log debug "Checking for newer version of '$sigfile'"
480         else
481                 log debug "'$sigfile' does not exist, so doing initial download"
482         fi
483
484         # Rsync will download the file only if it is different from the one in
485         # $clam_db_dir. The downloaded file will be stored in $tmp_dir.
486         run_cmd "$rsync" --stats -t --compare-dest="$clam_db_dir/" \
487                 "$url" "$new_sigfile"
488         local exit_status=$?
489         if [ $exit_status -ne 0 ]; then
490                 log err "Rsync had a problem getting '$new_sigfile' from '$url', exit status: $exit_status"
491                 rm -f "$new_sigfile"
492         fi
493
494         check_and_install_sigfile "$filename"
495 }
496
497 ####################################################################
498 # Startup functions
499 #
500
501 # Check for the external programs/tools and
502 # set the corresponding variables to the found paths
503 #
504 # Usage: check_for_external_programs
505 #
506 check_for_external_programs()
507 {
508         # Look for the paths to the programs we are going to need
509         logger=`type -P logger`
510         clamscan=`type -P clamscan`
511         curl=`type -P curl`
512         gunzip=`type -P gunzip`
513         rsync=`type -P rsync`
514
515         # Check if the 'logger' binary is missing
516         if [ -z "$logger" -o ! -x "$logger" ]; then
517                 unset logger
518                 log err "Could not find the 'logger' program, no syslog will be attempted"
519         fi
520
521         # If we did not find any of our external programs
522         # give an error message and exit
523         local prg
524         for prg in clamscan curl gunzip rsync; do
525                 if [ -z ${!prg} ]; then
526                         log err "Cannot find '$prg'"
527                         log err "Exiting."
528                         exit 1
529                 fi
530         done
531 }
532
533 # Print usage message.
534 #
535 # Usage: print_usage
536 #
537 print_usage()
538 {
539         echo -e "Downloads unofficial ClamAV signature files from sanesecurity.com and msrbl.com."
540         echo -e "Usage: $0 [options]"
541         echo -e "OPTIONS:"
542         echo -e "  --syslog-loglevel=level\tSets the log level for syslog to 'level'."
543         echo -e "  --stderr-loglevel=level\tSets the log level for stderr to 'level'."
544         echo -e "  --debug\t\t\tShorthand for '--syslog-loglevel=none"
545         echo -e "  \t\t\t\t--stderr-loglevel=debug'."
546         echo -e "  --sleep\t\t\tEnable the (random length) sleep before"
547         echo -e "  \t\t\t\tstarting the download. (this is the default)"
548         echo -e "  --no-sleep\t\t\tDisable the (random length) sleep before"
549         echo -e "  \t\t\t\tstarting the download. (default if stdin is"
550         echo -e "  \t\t\t\ta tty)"
551         echo -e ""
552         echo -e "Log level can be one of these:"
553         echo -e "  debug, info, notice, warning, err, crit, alert, emerg"
554 }
555
556 # Parse options/arguments
557 #
558 # Usage: parse_arguments
559 #
560 parse_arguments()
561 {
562         local arg
563
564         # Parse options/arguments
565         while [ $# -ge 1 ]; do
566                 case "$1" in
567                         --debug|-d|debug|Debug|DEBUG)
568                                 syslog_loglevel=none
569                                 stderr_loglevel=debug
570                                 log debug "Debug mode is ON"
571                                 ;;
572                         --syslog-loglevel)
573                                 syslog_loglevel=$2
574                                 shift
575                                 ;;
576                         --syslog-loglevel=*)
577                                 syslog_loglevel=${1#--*=}
578                                 ;;
579                         --stderr-loglevel)
580                                 stderr_loglevel=$2
581                                 shift
582                                 ;;
583                         --stderr-loglevel=*)
584                                 stderr_loglevel=${1#--*=}
585                                 ;;
586                         --sleep)
587                                 no_sleep=0
588                                 ;;
589                         --no-sleep)
590                                 no_sleep=1
591                                 ;;
592                         --unprivileged-child)
593                                 unprivileged_child=1
594                                 ;;
595                         --help|-h|-\?)
596                                 print_usage
597                                 exit 0
598                                 ;;
599                         *)
600                                 log err "Got command line argument of '$1' and I don't understand it!"
601                                 log err "Exiting."
602                                 exit 1
603                                 ;;
604                 esac
605                 shift
606         done
607 }
608
609 # Create a temporary directory and arrange to remove it on exit,
610 # plus create an empty file to use for testing ClamScan
611 #
612 # Usage: create_temp_dir
613 #
614 create_temp_dir()
615 {
616         tmp_dir=`mktemp -d -t ${program_invocation_short_name}.XXXXXXXX` || (
617                 log err "Cannot create temporary directory"
618                 log err "Exiting."
619                 exit 1
620         )
621         local exit_status=$?
622         if [ $exit_status -ne 0 ]; then
623                 log err "Error running mktemp(1), exit status: $exit_status"
624                 log err "Exiting."
625                 exit 1
626         fi
627         log debug "Created temporary directory: '$tmp_dir'"
628         if [ $keep_temp_dir -eq 0 ]; then
629                 trap 'rm -rf "$tmp_dir"' EXIT
630         fi
631
632         # We create a file for ClamScan to test in debug mode.
633         test_file="$tmp_dir/test.file"
634         touch "$test_file"
635 }
636
637 # Log a couple of debug messages with a summary on some parameters
638 #
639 # Usage: log_startup_summary
640 #
641 log_startup_summary()
642 {
643         log debug "PHISH_SIGS    : $PHISH_SIGS_URL"
644         log debug "SCAM_SIGS     : $SCAM_SIGS_URL"
645         log debug "SPAM_SIGS     : $MSRBL_SPAM_SIGS_URL"
646         log debug "IMAGE_SIGS    : $MSRBL_IMAGE_SIGS_URL"
647         log debug "ClamScan      : $clamscan"
648         log debug "CURL          : $curl"
649         log debug "GunZip        : $gunzip"
650         log debug "RSync         : $rsync"
651         log debug "ClamAV db dir : $clam_db_dir"
652         log debug "temp dir      : $tmp_dir"
653 }
654
655 # Sleep for a random time (determined by $min_sleep_time and $max_sleep_time global variables)
656 #
657 # Usage: random_sleep
658 #
659 random_sleep()
660 {
661         local sleep_time
662
663         sleep_time=$(($RANDOM * $(($max_sleep_time-$min_sleep_time)) / 32767 + $min_sleep_time))
664         log debug "Sleeping for $sleep_time seconds..."
665         sleep $sleep_time
666 }
667
668 # Find the ClamAV db dir and set the $clam_db_dir global variable
669 #
670 # Usage: find_clam_db_dir
671 #
672 find_clam_db_dir()
673 {
674         # Scan an empty test file with debug enabled to determine where ClamAV expects
675         # to find it's signature database
676         log debug "Checking for ClamAV database directory..."
677         clam_db_dir=`"$clamscan" --debug "$test_file" 2>&1 | \
678                 sed -ne 's/\/$//; s/^.*loading databases from \(.*\)$/\1/ip' | head -1`
679         log debug "Found ClamAV database directory: $clam_db_dir"
680
681         # Check for either the daily.inc, the main.inc dirs or the main.cvd one of which
682         # must exist for a functional clamav installation
683         if [ \
684           ! -d "$clam_db_dir/daily.inc" -a \
685           ! -d "$clam_db_dir/main.inc" -a \
686           ! -f "$clam_db_dir/main.cvd" -a \
687           ! -f "$clam_db_dir/main.cld" \
688         ]; then
689                 log err "None of '$clam_db_dir/daily.inc', '$clam_db_dir/main.inc',"
690                 log err "'$clam_db_dir/main.cvd', '$clam_db_dir/main.cld' found"
691                 log err "in your database directory. Either '$clam_db_dir' is NOT"
692                 log err "the correct database path or there is something wrong with your"
693                 log err "ClamAV installation. This path came from your '$clamscan' so I would guess"
694                 log err "you need to check your clamd.conf file and/or '$clam_db_dir'"
695                 log err "Exiting."
696                 exit 1
697         fi
698 }
699
700 #
701 # Change owner, group and security context of the signature files.
702 #
703 # Usage: chown_chcon sigfiles ...
704 #
705 chown_chcon()
706 {
707         local -a sigfiles='("$@")'
708
709         for i in "${sigfiles[@]}"; do
710                 [ -f "$i" ] || continue
711
712                 run_cmd chown $sigfile_owner_and_group "$i"
713                 exit_status=$?
714                 if [ $exit_status -ne 0 ]; then
715                         log err "chown had a problem changing ownership of signature file '$i', exit status: $exit_status"
716                         log err "Exiting."
717                         exit 1
718                 fi
719         done
720
721         # SELinux fix: change security context
722         if [ -n "$(type -P sestatus 2>/dev/null)" ] && [ "$(sestatus | head -n 1 | awk '{ print $3 }')" == "enabled" ]; then
723                 for i in "${sigfiles[@]}"; do
724                         [ -f "$i" ] || continue
725
726                         run_cmd chcon user_u:object_r:var_t "$i"
727                         exit_status=$?
728                         if [ $exit_status -ne 0 ]; then
729                                 log err "chcon had a problem changing security context of signature file '$i', exit status: $exit_status"
730                                 log err "Exiting."
731                                 exit 1
732                         fi
733                 done
734         fi
735 }
736
737 # Reload the ClamAV daemon
738 #
739 # Usage: reload_clamav_daemon
740 #
741 reload_clamav_daemon()
742 {
743         local -a clamd_reload_cmd
744         if [ -n "`type -P service`" ]; then
745                 clamd_reload_cmd=(service clamd reload)
746         else
747                 for init_script in /etc/{init,rc}.d/{clamd,clamav-daemon}; do
748                         if [ -x "$init_script" ]; then
749                                 clamd_reload_cmd=("$init_script" reload-database)
750                                 break
751                         fi
752                 done
753         fi
754         if [ -n "${clamd_reload_cmd[*]}" ]; then
755                 log info "Reloading ClamAV daemon"
756                 run_cmd "${clamd_reload_cmd[@]}"
757         else
758                 log err "Cannot reload ClamAV daemon because no initscript found"
759                 return 1
760         fi
761 }
762
763 ####################################################################
764
765
766 declare logger clamscan curl gunzip rsync
767 declare tmp_dir test_file
768 declare clam_db_dir
769 declare unprivileged_child=0
770 declare no_sleep=0
771
772 # Skip sleeping if run interactively
773 if tty -s; then
774         log debug "Disabling random sleep feature because stdin is a terminal."
775         no_sleep=1
776 fi
777
778 # The short name of this script
779 readonly program_invocation_short_name=`basename "$0"`
780
781 # The absolute name of this script
782 readonly program_invocation_absolute_name=$(readlink -f "$0" || type -P "$0")
783
784 # Startup
785 parse_arguments "$@"
786 if [ "$unprivileged_child" -eq 0 ]; then
787         log debug "Starting."
788 fi
789 check_for_external_programs
790 create_temp_dir
791 find_clam_db_dir
792 if [ "$unprivileged_child" -eq 0 ]; then
793         log_startup_summary
794         if [ "$no_sleep" -eq 0 ]; then
795                 random_sleep
796         fi
797 fi
798
799 # Change current directory to ClamAV database directory
800 # but just for the sake of safety we will use
801 # absolute paths for copy/move operations
802 cd "$clam_db_dir"
803
804 declare sigfile_updated=0
805 if [ "$unprivileged_child" -ne 0 -o $(id -u) -ne 0 ]; then
806         # Update/download the signature files
807         update_sigfile_with_curl "$SCAM_SIGS_URL" "$SCAM_SIGS" && sigfile_updated=1
808         update_sigfile_with_curl "$PHISH_SIGS_URL" "$PHISH_SIGS" && sigfile_updated=1
809         update_sigfile_with_rsync "$MSRBL_SPAM_SIGS_URL" "$MSRBL_SPAM_SIGS" && sigfile_updated=1
810         update_sigfile_with_rsync "$MSRBL_IMAGE_SIGS_URL" "$MSRBL_IMAGE_SIGS" && sigfile_updated=1
811 else
812         # Re-execute the script as the unprivileged user to do the download/check/install part.
813         # (It exits with 0 exit status only if at least on the signature file were updated.)
814         su -s $SHELL $unprivileged_user -c "'$program_invocation_absolute_name' --unprivileged-child --syslog-loglevel=$syslog_loglevel --stderr-loglevel=$stderr_loglevel" && sigfile_updated=1
815
816         # Change owner, group and security context.
817         chown_chcon "$SCAM_SIGS" "$PHISH_SIGS" "$MSRBL_SPAM_SIGS" "$MSRBL_IMAGE_SIGS"
818 fi
819
820 # Reload database
821 if [ $reload_clamd -ne 0 -a $sigfile_updated -ne 0 -a "$unprivileged_child" -eq 0 ]; then
822         reload_clamav_daemon
823 fi
824
825 if [ "$unprivileged_child" -eq 0 ]; then
826         log debug "Exiting."
827 fi
828 if [ $sigfile_updated -ne 0 ]; then
829         final_exit_status=0
830 else
831         final_exit_status=100
832 fi
833 exit $final_exit_status