001/*
002 *  Copyright 2023 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016
017package org.ametys.runtime.servlet;
018
019import java.io.BufferedReader;
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.InputStreamReader;
023import java.nio.charset.StandardCharsets;
024import java.util.Arrays;
025import java.util.stream.Collectors;
026
027import org.apache.commons.lang.StringUtils;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031import org.ametys.runtime.config.Config;
032
033/**
034 * Helper that analyzes a file for viruses
035 */
036public final class AnalyseFileForVirusHelper
037{
038    /** The result of the antivirus when no viruses is found */
039    public static final Integer ANTIVIRUS_RESULT_OK = 0;
040    
041    private static final Logger __LOGGER = LoggerFactory.getLogger(AnalyseFileForVirusHelper.class);
042    
043    private AnalyseFileForVirusHelper()
044    {
045        // utility class
046    }
047    
048    /**k
049     * Checks if the antivirus is enabled
050     * @return <code>true</code> if the antivirus is enabled, <code>false</code> otherwise 
051     */
052    public static boolean isAntivirusEnabled()
053    {
054        return Config.getInstance().getValue("runtime.upload.antivirus.activated", false, false);
055    }
056    
057    /**
058     * Antivirus analysis. Based on clamscan results.
059     * 
060     * @param absolutePath the absolute path of the file to analyse
061     * @return true if the file is correct, false if a malware was discovered in
062     *         the file
063     */
064    public static boolean analysefile(String absolutePath)
065    {
066        if (!isAntivirusEnabled())
067        {
068            return true;
069        }
070        
071        try
072        {
073            String command = Config.getInstance().getValue("runtime.upload.antivirus.command");
074            // Split before replacing with file path to avoid issue with space in file name
075            String[] cmdArray = StringUtils.split(command);
076            cmdArray = Arrays.stream(cmdArray).map(str -> StringUtils.replace(str, "%f", absolutePath)).toArray(String[]::new);
077            if (__LOGGER.isDebugEnabled())
078            {
079                __LOGGER.debug("Executing antivirus analysis : {}", StringUtils.join(cmdArray, " "));
080            }
081            
082            // Execute command
083            // use the String[] constructor to unsure correct handling of filename with special character
084            Process child = new ProcessBuilder(cmdArray)
085                .redirectErrorStream(true)
086                .start();
087            child.waitFor();
088            
089            // Get the input stream and read from it
090            if (__LOGGER.isDebugEnabled() || child.exitValue() == 2)
091            {
092                StringBuilder builder = new StringBuilder("Result of the command : (").append(child.exitValue()).append(")\n");
093                try (InputStream in = child.getInputStream())
094                {
095                    String cmdOutput = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))
096                        .lines()
097                        .collect(Collectors.joining("\n"));
098                    builder.append(cmdOutput);
099                }
100                if (child.exitValue() == 2)
101                {
102                    __LOGGER.error(builder.toString());
103                }
104                else
105                {
106                    __LOGGER.debug(builder.toString());
107                }
108            }
109            return ANTIVIRUS_RESULT_OK.equals(child.exitValue());
110        }
111        catch (IOException | InterruptedException e)
112        {
113            __LOGGER.error("Unable to get output from the command", e);
114            return false;
115        }
116    }
117}