55import exceptions .ExceptionHandler ;
66
77import java .io .BufferedReader ;
8+ import java .io .FileWriter ;
89import java .io .InputStreamReader ;
10+ import java .io .PrintWriter ;
911import java .nio .file .Files ;
1012import java .nio .file .Path ;
1113import java .security .MessageDigest ;
14+ import java .time .LocalDateTime ;
15+ import java .time .format .DateTimeFormatter ;
1216import java .util .HexFormat ;
1317import java .util .Map ;
1418import java .util .concurrent .ConcurrentHashMap ;
@@ -82,6 +86,9 @@ public AntivirusScanner()
8286 CommonRails .printSystemComponent (this , this .hashCode (), ". AntivirusScanner initialized — schedule=" + this .schedule + " path=" + this .scanPath + " ." );
8387 }
8488
89+ /** Log file for ClamAV update/shutdown warnings. */
90+ private static final Path CLAMAV_LOG = Path .of ("logging/clamav.log" );
91+
8592 /** Start the scheduled executor. Returns immediately; scans run in background. */
8693 public void start ()
8794 {
@@ -97,6 +104,71 @@ public void start()
97104 return t ;
98105
99106 }).scheduleAtFixedRate (this ::scan , 0 , period , TimeUnit .SECONDS );
107+
108+ // Schedule a freshclam database update 20 seconds after server load
109+ Executors .newSingleThreadScheduledExecutor (r ->
110+ {
111+ Thread t = new Thread (r , "ClamAV-Updater" );
112+
113+ t .setDaemon (true );
114+
115+ return t ;
116+
117+ }).schedule (this ::updateDefinitions , 20 , TimeUnit .SECONDS );
118+ }
119+
120+ /** Run freshclam to update ClamAV virus definitions; log warnings to logging/clamav.log. */
121+ private void updateDefinitions ()
122+ {
123+ CommonRails .printSystemComponent (this , this .hashCode (), ". AntivirusScanner >> freshclam database update starting ." );
124+
125+ try
126+ {
127+ Files .createDirectories (CLAMAV_LOG .getParent ());
128+
129+ Process p = new ProcessBuilder ("freshclam" )
130+ .redirectErrorStream (true )
131+ .start ();
132+
133+ String output = new BufferedReader (new InputStreamReader (p .getInputStream ()))
134+ .lines ()
135+ .collect (java .util .stream .Collectors .joining ("\n " ));
136+
137+ int exit = p .waitFor ();
138+
139+ // Write all output (including warnings) to the clamav log
140+ try (PrintWriter pw = new PrintWriter (new FileWriter (CLAMAV_LOG .toFile (), true )))
141+ {
142+ pw .println ("[" + LocalDateTime .now ().format (DateTimeFormatter .ISO_LOCAL_DATE_TIME ) + "] freshclam update (exit=" + exit + ")" );
143+ pw .println (output );
144+ pw .println ();
145+ }
146+
147+ if (exit == 0 )
148+ CommonRails .printSystemComponent (this , this .hashCode (), ". AntivirusScanner >> freshclam update completed successfully ." );
149+ else
150+ CommonRails .printSystemComponent (this , this .hashCode (), ". AntivirusScanner >> freshclam update finished with warnings (exit=" + exit + ") — see logging/clamav.log ." );
151+ }
152+ catch (Exception e )
153+ {
154+ logClamWarning ("freshclam update failed: " + e .getMessage ());
155+ ExceptionHandler .dispatch (e );
156+ }
157+ }
158+
159+ /** Append a warning line to the ClamAV log file. */
160+ public static void logClamWarning (final String message )
161+ {
162+ try
163+ {
164+ Files .createDirectories (CLAMAV_LOG .getParent ());
165+
166+ try (PrintWriter pw = new PrintWriter (new FileWriter (CLAMAV_LOG .toFile (), true )))
167+ {
168+ pw .println ("[" + LocalDateTime .now ().format (DateTimeFormatter .ISO_LOCAL_DATE_TIME ) + "] " + message );
169+ }
170+ }
171+ catch (Exception ignored ) {}
100172 }
101173
102174 private void scan ()
@@ -112,6 +184,8 @@ private void runClamScan()
112184 {
113185 if (!clamAvailable ())
114186 {
187+ logClamWarning ("clamscan not found on PATH — skipping AV scan" );
188+
115189 CommonRails .printSystemComponent (this , this .hashCode (), ". AntivirusScanner >> clamscan not found on PATH — skipping AV scan ." );
116190
117191 return ;
@@ -132,11 +206,14 @@ private void runClamScan()
132206 }
133207 else
134208 {
135- CommonRails .printSystemComponent (this , this .hashCode (), ". AntivirusScanner >> ClamAV ALERT (exit=" + exit + "):\n " + out + "\n " + err + " ." );
209+ logClamWarning ("ClamAV ALERT (exit=" + exit + "): " + out + " " + err );
210+
211+ CommonRails .printSystemComponent (this , this .hashCode (), ". AntivirusScanner >> ClamAV ALERT (exit=" + exit + ") — see logging/clamav.log ." );
136212 }
137213 }
138214 catch (Exception e )
139215 {
216+ logClamWarning ("ClamAV scan exception: " + e .getMessage ());
140217 ExceptionHandler .dispatch (e );
141218 }
142219 }
0 commit comments