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.lang3.StringUtils;
028import org.apache.commons.lang3.Strings;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032import org.ametys.runtime.config.Config;
033
034/**
035 * Helper that analyzes a file for viruses
036 */
037public final class AnalyseFileForVirusHelper
038{
039    /** The result of the antivirus when no viruses is found */
040    public static final Integer ANTIVIRUS_RESULT_OK = 0;
041    
042    private static final Logger __LOGGER = LoggerFactory.getLogger(AnalyseFileForVirusHelper.class);
043    
044    private AnalyseFileForVirusHelper()
045    {
046        // utility class
047    }
048    
049    /**k
050     * Checks if the antivirus is enabled
051     * @return <code>true</code> if the antivirus is enabled, <code>false</code> otherwise 
052     */
053    public static boolean isAntivirusEnabled()
054    {
055        return Config.getInstance().getValue("runtime.upload.antivirus.activated", false, false);
056    }
057    
058    /**
059     * Antivirus analysis. Based on clamscan results.
060     * 
061     * @param absolutePath the absolute path of the file to analyse
062     * @return true if the file is correct, false if a malware was discovered in
063     *         the file
064     */
065    public static boolean analysefile(String absolutePath)
066    {
067        if (!isAntivirusEnabled())
068        {
069            return true;
070        }
071        
072        try
073        {
074            String command = Config.getInstance().getValue("runtime.upload.antivirus.command");
075            // Split before replacing with file path to avoid issue with space in file name
076            String[] cmdArray = StringUtils.split(command);
077            cmdArray = Arrays.stream(cmdArray).map(str -> Strings.CS.replace(str, "%f", absolutePath)).toArray(String[]::new);
078            if (__LOGGER.isDebugEnabled())
079            {
080                __LOGGER.debug("Executing antivirus analysis : {}", StringUtils.join(cmdArray, " "));
081            }
082            
083            // Execute command
084            // use the String[] constructor to unsure correct handling of filename with special character
085            Process child = new ProcessBuilder(cmdArray)
086                .redirectErrorStream(true)
087                .start();
088            child.waitFor();
089            
090            // Get the input stream and read from it
091            if (__LOGGER.isDebugEnabled() || child.exitValue() == 2)
092            {
093                StringBuilder builder = new StringBuilder("Result of the command : (").append(child.exitValue()).append(")\n");
094                try (InputStream in = child.getInputStream())
095                {
096                    String cmdOutput = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))
097                        .lines()
098                        .collect(Collectors.joining("\n"));
099                    builder.append(cmdOutput);
100                }
101                if (child.exitValue() == 2)
102                {
103                    __LOGGER.error(builder.toString());
104                }
105                else
106                {
107                    __LOGGER.debug(builder.toString());
108                }
109            }
110            return ANTIVIRUS_RESULT_OK.equals(child.exitValue());
111        }
112        catch (IOException | InterruptedException e)
113        {
114            __LOGGER.error("Unable to get output from the command", e);
115            return false;
116        }
117    }
118}