Package org.zanata.rest.service

Source Code of org.zanata.rest.service.VirusScanner

/*
* Copyright 2013, Red Hat, Inc. and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/

package org.zanata.rest.service;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteStreamHandler;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.Executor;
import org.apache.commons.exec.PumpStreamHandler;
import org.jboss.seam.annotations.Name;
import org.zanata.exception.VirusDetectedException;

import com.google.common.base.Stopwatch;

/**
* <code>VirusScanner</code> scans files using ClamAV's <code>clamdscan</code>
* command if available, or a different scanner if configured.
* <p>
* By default, <code>VirusScanner</code> looks for <code>clamdscan</code> on the
* system path, but this can be overridden with the system property
* <code>virusScanner</code>, either with a full path such as
* <code>/usr/bin/clamdscan</code>, or the name of another scanner entirely.
* <code>clamdscan</code> depends on the <code>clamd</code> service, so this
* class will throw a <code>RuntimeException</code> if <code>clamdscan</code> is
* found but <code>clamd</code> is not running.
* <p>
* If the system property has been set, a failure to launch the scanner will
* cause an exception. (If it has not been set, an error will be logged but that
* is all.)
*
* @author Sean Flanigan <a
*         href="mailto:sflaniga@redhat.com">sflaniga@redhat.com</a>
*
*/
@Name("virusScanner")
@Slf4j
public class VirusScanner {
    private static final boolean DISABLED;
    private static final boolean SCANNER_SET;
    private static final String SCANNER_NAME;
    private static final boolean USING_CLAM;
    // clamdscan-specific options:
    private static final String[] CLAMDSCAN_ARGS = {
            // just makes the error messages less verbose
            "--no-summary",
            // This ensures that clamd can scan the file regardless of
            // permissions or security context (since clamdscan actually
            // accesses the file, not clamd):
            "--stream"
    };

    static {
        // If the system property is empty or null, we try to use
        // clamdscan, but we don't throw an exception if we can't find it.
        // clamscan would work too, but takes ~15 seconds
        String scannerProperty = System.getProperty("virusScanner");
        DISABLED = "DISABLED".equals(scannerProperty);
        if (DISABLED) {
            log.warn("virus scanning disabled");
            SCANNER_SET = true;
            SCANNER_NAME = scannerProperty;
            USING_CLAM = false;
        } else {
            if (scannerProperty == null || scannerProperty.isEmpty()) {
                SCANNER_NAME = "clamdscan";
                SCANNER_SET = false;
                log.info("defaulting to clamdscan (system property 'virusScanner' is null or empty)");
                log.warn("failure to run scanner will be logged but otherwise ignored");
            } else {
                SCANNER_NAME = scannerProperty;
                SCANNER_SET = true;
                log.info("property 'virusScanner' found: failure to run scanner will be treated as an error");
            }
            USING_CLAM = SCANNER_NAME.toLowerCase().contains("clamdscan");
            if (USING_CLAM) {
                log.info("scanning with command '{}', using arguments: {}",
                        SCANNER_NAME, CLAMDSCAN_ARGS);
            } else {
                log.info("scanning with command '{}'", SCANNER_NAME);
            }
        }
    }

    public static boolean isDisabled() {
        return DISABLED;
    }

    public static boolean isScannerSet() {
        return SCANNER_SET;
    }

    /**
     * Scans the specified file by calling out to ClamAV, unless disabled by
     * system property.
     * <p>
     * Note 1: the file will be made world readable, so that clamd can access
     * it.
     * <p>
     * Note 2: the caller is responsible for deleting the file.
     *
     * @param file
     *            file to be scanned (probably a temp file)
     * @param documentName
     *            human-friendly name for the file
     * @throws VirusDetectedException
     *             if the scanner detects a virus
     * @throws RuntimeException
     *             if something else goes wrong (eg can't execute virus scanner)
     */
    public void scan(File file, String documentName)
            throws VirusDetectedException {
        if (DISABLED) {
            log.debug("file not scanned: {}", documentName);
        } else {
            doScan(file, documentName);
        }
    }

    private void doScan(File file, String documentName) {
        Stopwatch stop = new Stopwatch().start();
        CommandLine cmdLine = buildCommandLine(file);
        ByteArrayOutputStream scannerOutput = new ByteArrayOutputStream();
        Executor executor = buildExecutor(scannerOutput);
        try {
            int exitValue = executor.execute(cmdLine);
            log.debug("{} to scan file: '{}'", stop, documentName);
            handleResult(exitValue, documentName, scannerOutput);
        } catch (IOException e) {
            // perhaps the antivirus executable was not found...
            // we omit the stack exception, because it tends to be uninteresting
            // in this case
            String msg =
                    "error executing " + SCANNER_NAME
                            + ", unable to scan file '" + documentName
                            + "' for viruses: " + e.getMessage();
            if (SCANNER_SET) {
                throw new RuntimeException(msg);
            }
            log.error(msg);
        }
    }

    private void
            handleResult(int exitValue, String documentName, Object output) {
        // The following return codes are taken from the clamdscan manpage.
        // If another scanner is used, we may not use the ideal
        // exception when something goes wrong, but as long as zero
        // still means "no virus" it should be okay.
        final int noVirusFound = 0;
        final int virusFound = 1;
        final int someErrorOccurred = 2;
        switch (exitValue) {
        case noVirusFound:
            log.info("{} says file '{}' is clean: {}", SCANNER_NAME,
                    documentName, output);
            return;
        case virusFound:
            throw new VirusDetectedException(SCANNER_NAME + " detected virus: "
                    + output);
        case someErrorOccurred:
        default:
            // This can happen if clamdscan is found, but the clamd service is
            // not running.
            String msg =
                    SCANNER_NAME
                            + " returned error scanning file '"
                            + documentName
                            + "': "
                            + output
                            + (USING_CLAM ? "\nPlease ensure clamd service is running."
                                    : "");
            throw new RuntimeException(msg);
        }
    }

    private CommandLine buildCommandLine(File file) {
        CommandLine cmdLine = new CommandLine(SCANNER_NAME);
        if (USING_CLAM) {
            cmdLine.addArguments(CLAMDSCAN_ARGS, false);
        }
        cmdLine.addArgument(file.getPath(), false);
        return cmdLine;
    }

    /**
     * Builds an Executor which will output to the specified OutputStream.
     * <p>
     * The Executor will be configured to return exit values as int, rather than
     * throwing ExecuteException.
     *
     * @param output
     * @return a configured Executor
     */
    private Executor buildExecutor(OutputStream output) {
        DefaultExecutor executor = new DefaultExecutor();
        ExecuteWatchdog watchdog = new ExecuteWatchdog(60000);
        executor.setWatchdog(watchdog);
        ExecuteStreamHandler psh = new PumpStreamHandler(output);
        executor.setStreamHandler(psh);
        // We want to handle all exit values directly (not as ExecuteException).
        executor.setExitValues(null);
        return executor;
    }

}
TOP

Related Classes of org.zanata.rest.service.VirusScanner

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.