/** * The contents of this file are subject to the license and copyright * detailed in the LICENSE file at the root of the source * tree and available online at * * https://github.com/keeps/roda */ package org.roda.core.plugins.plugins.antivirus; import java.util.Arrays; import java.util.List; import org.apache.commons.io.IOUtils; import org.roda.core.RodaCoreFactory; import org.roda.core.data.common.RodaConstants; import org.roda.core.data.common.RodaConstants.PreservationEventType; import org.roda.core.data.exceptions.AlreadyExistsException; import org.roda.core.data.exceptions.AuthorizationDeniedException; import org.roda.core.data.exceptions.GenericException; import org.roda.core.data.exceptions.NotFoundException; import org.roda.core.data.exceptions.RODAException; import org.roda.core.data.exceptions.RequestNotValidException; import org.roda.core.data.v2.LiteOptionalWithCause; import org.roda.core.data.v2.ip.AIP; import org.roda.core.data.v2.ip.AIPState; import org.roda.core.data.v2.ip.StoragePath; import org.roda.core.data.v2.jobs.Job; import org.roda.core.data.v2.jobs.PluginType; import org.roda.core.data.v2.jobs.Report; import org.roda.core.data.v2.jobs.Report.PluginState; import org.roda.core.data.v2.validation.ValidationException; import org.roda.core.index.IndexService; import org.roda.core.model.ModelService; import org.roda.core.model.utils.ModelUtils; import org.roda.core.plugins.AbstractPlugin; import org.roda.core.plugins.Plugin; import org.roda.core.plugins.PluginException; import org.roda.core.plugins.RODAObjectProcessingLogic; import org.roda.core.plugins.orchestrate.SimpleJobPluginInfo; import org.roda.core.plugins.plugins.PluginHelper; import org.roda.core.storage.DirectResourceAccess; import org.roda.core.storage.StorageService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class AntivirusPlugin extends AbstractPlugin<AIP> { private static final Logger LOGGER = LoggerFactory.getLogger(AntivirusPlugin.class); private String antiVirusClassName; private AntiVirus antiVirus = null; @Override public void init() throws PluginException { antiVirusClassName = RodaCoreFactory.getRodaConfiguration().getString( "core.plugins.internal.virus_check.antiVirusClassname", "org.roda.core.plugins.plugins.antivirus.ClamAntiVirus"); try { LOGGER.debug("Loading antivirus class {}", antiVirusClassName); setAntiVirus((AntiVirus) Class.forName(antiVirusClassName).newInstance()); LOGGER.debug("Using antivirus {}", getAntiVirus().getClass().getName()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { LOGGER.warn("Error loading antivirus", e); } if (getAntiVirus() == null) { setAntiVirus(new AVGAntiVirus()); LOGGER.warn("Using default antivirus {}", getAntiVirus().getClass().getName()); } LOGGER.debug("init OK"); } @Override public void shutdown() { // do nothing } public static String getStaticName() { return "AIP Virus check"; } @Override public String getName() { return getStaticName(); } public static String getStaticDescription() { return "Scans Information Package(s) for malicious software using the Antivirus application ClamAV. Clam AntiVirus (ClamAV) is a free and open-source, cross-platform antivirus software toolkit able to detect many types of malicious software, including viruses.\nIf malicious software is detected a report will be generated and a PREMIS event will record this occurrence."; } @Override public String getDescription() { return getStaticDescription(); } @Override public String getVersionImpl() { if (antiVirus != null) { return antiVirus.getVersion(); } return "1.0"; } @Override public Report execute(IndexService index, ModelService model, StorageService storage, List<LiteOptionalWithCause> liteList) throws PluginException { return PluginHelper.processObjects(this, new RODAObjectProcessingLogic<AIP>() { @Override public void process(IndexService index, ModelService model, StorageService storage, Report report, Job cachedJob, SimpleJobPluginInfo jobPluginInfo, Plugin<AIP> plugin, AIP object) { processAIP(index, model, storage, report, jobPluginInfo, cachedJob, object); } }, index, model, storage, liteList); } private void processAIP(IndexService index, ModelService model, StorageService storage, Report report, SimpleJobPluginInfo jobPluginInfo, Job job, AIP aip) { Report reportItem = PluginHelper.initPluginReportItem(this, aip.getId(), AIP.class, AIPState.INGEST_PROCESSING); PluginHelper.updatePartialJobReport(this, model, reportItem, false, job); PluginState reportState = PluginState.SUCCESS; VirusCheckResult virusCheckResult = null; Exception exception = null; DirectResourceAccess directAccess = null; try { LOGGER.debug("Checking if AIP {} is clean of virus", aip.getId()); StoragePath aipPath = ModelUtils.getAIPStoragePath(aip.getId()); directAccess = storage.getDirectAccess(aipPath); virusCheckResult = getAntiVirus().checkForVirus(directAccess.getPath()); reportState = virusCheckResult.isClean() ? PluginState.SUCCESS : PluginState.FAILURE; reportItem.setPluginState(reportState).setPluginDetails(virusCheckResult.getReport()); LOGGER.debug("Done with checking if AIP {} has virus. Is clean of virus: {}", aip.getId(), virusCheckResult.isClean()); } catch (RODAException | RuntimeException e) { LOGGER.error("Error processing AIP " + aip.getId(), e); reportState = PluginState.FAILURE; reportItem.setPluginState(reportState).setPluginDetails(e.getMessage()); exception = e; } finally { IOUtils.closeQuietly(directAccess); jobPluginInfo.incrementObjectsProcessed(reportState); try { createEvent(virusCheckResult, exception, reportItem.getPluginState(), aip, model, index, true); report.addReport(reportItem); PluginHelper.updatePartialJobReport(this, model, reportItem, true, job); } catch (PluginException e) { LOGGER.error("Error updating event and job", e); } } } private void createEvent(VirusCheckResult virusCheckResult, Exception exception, PluginState state, AIP aip, ModelService model, IndexService index, boolean notify) throws PluginException { try { StringBuilder outcomeDetailExtension = new StringBuilder(); outcomeDetailExtension.append(virusCheckResult == null ? "" : virusCheckResult.getReport()); if (state != PluginState.SUCCESS && exception != null) { outcomeDetailExtension.append("\n").append(exception.getClass().getName()).append(": ") .append(exception.getMessage()); } PluginHelper.createPluginEvent(this, aip.getId(), model, index, state, outcomeDetailExtension.toString(), notify); } catch (RequestNotValidException | NotFoundException | GenericException | AuthorizationDeniedException | ValidationException | AlreadyExistsException e) { throw new PluginException("Error while creating the event", e); } } public String getAntiVirusClassName() { return antiVirusClassName; } public void setAntiVirusClassName(String antiVirusClassName) { this.antiVirusClassName = antiVirusClassName; } public AntiVirus getAntiVirus() { return antiVirus; } public void setAntiVirus(AntiVirus antiVirus) { this.antiVirus = antiVirus; } @Override public Plugin<AIP> cloneMe() { AntivirusPlugin antivirusPlugin = new AntivirusPlugin(); antivirusPlugin.setAntiVirus(getAntiVirus()); return antivirusPlugin; } @Override public PluginType getType() { return PluginType.AIP_TO_AIP; } @Override public boolean areParameterValuesValid() { return true; } @Override public PreservationEventType getPreservationEventType() { return PreservationEventType.VIRUS_CHECK; } @Override public String getPreservationEventDescription() { return "Scanned package for malicious programs using ClamAV."; } @Override public String getPreservationEventSuccessMessage() { return "The package does not contain any known malicious programs."; } @Override public String getPreservationEventFailureMessage() { return "A malicious program was detected inside the package."; } @Override public Report beforeAllExecute(IndexService index, ModelService model, StorageService storage) throws PluginException { // do nothing return null; } @Override public Report afterAllExecute(IndexService index, ModelService model, StorageService storage) throws PluginException { // do nothing return null; } @Override public List<String> getCategories() { return Arrays.asList(RodaConstants.PLUGIN_CATEGORY_VALIDATION, RodaConstants.PLUGIN_CATEGORY_CHARACTERIZATION); } @Override public List<Class<AIP>> getObjectClasses() { return Arrays.asList(AIP.class); } }