Java tutorial
/** * Copyright ou ou Copr. Ministre de l'Europe et des Affaires trangres (2017) * <p/> * pole-architecture.dga-dsi-psi@diplomatie.gouv.fr * <p/> * Ce logiciel est un programme informatique servant faciliter la cration * d'applications Web conformment aux rfrentiels gnraux franais : RGI, RGS et RGAA * <p/> * Ce logiciel est rgi par la licence CeCILL soumise au droit franais et * respectant les principes de diffusion des logiciels libres. Vous pouvez * utiliser, modifier et/ou redistribuer ce programme sous les conditions * de la licence CeCILL telle que diffuse par le CEA, le CNRS et l'INRIA * sur le site "http://www.cecill.info". * <p/> * En contrepartie de l'accessibilit au code source et des droits de copie, * de modification et de redistribution accords par cette licence, il n'est * offert aux utilisateurs qu'une garantie limite. Pour les mmes raisons, * seule une responsabilit restreinte pse sur l'auteur du programme, le * titulaire des droits patrimoniaux et les concdants successifs. * <p/> * A cet gard l'attention de l'utilisateur est attire sur les risques * associs au chargement, l'utilisation, la modification et/ou au * dveloppement et la reproduction du logiciel par l'utilisateur tant * donn sa spcificit de logiciel libre, qui peut le rendre complexe * manipuler et qui le rserve donc des dveloppeurs et des professionnels * avertis possdant des connaissances informatiques approfondies. Les * utilisateurs sont donc invits charger et tester l'adquation du * logiciel leurs besoins dans des conditions permettant d'assurer la * scurit de leurs systmes et ou de leurs donnes et, plus gnralement, * l'utiliser et l'exploiter dans les mmes conditions de scurit. * <p/> * Le fait que vous puissiez accder cet en-tte signifie que vous avez * pris connaissance de la licence CeCILL, et que vous en avez accept les * termes. * <p/> * <p/> * Copyright or or Copr. Ministry for Europe and Foreign Affairs (2017) * <p/> * pole-architecture.dga-dsi-psi@diplomatie.gouv.fr * <p/> * This software is a computer program whose purpose is to facilitate creation of * web application in accordance with french general repositories : RGI, RGS and RGAA. * <p/> * This software is governed by the CeCILL license under French law and * abiding by the rules of distribution of free software. You can use, * modify and/ or redistribute the software under the terms of the CeCILL * license as circulated by CEA, CNRS and INRIA at the following URL * "http://www.cecill.info". * <p/> * As a counterpart to the access to the source code and rights to copy, * modify and redistribute granted by the license, users are provided only * with a limited warranty and the software's author, the holder of the * economic rights, and the successive licensors have only limited * liability. * <p/> * In this respect, the user's attention is drawn to the risks associated * with loading, using, modifying and/or developing or reproducing the * software by the user in light of its specific status of free software, * that may mean that it is complicated to manipulate, and that also * therefore means that it is reserved for developers and experienced * professionals having in-depth computer knowledge. Users are therefore * encouraged to load and test the software's suitability as regards their * requirements in conditions enabling the security of their systems and/or * data to be ensured and, more generally, to use and operate it in the * same conditions as regards security. * <p/> * The fact that you are presently reading this means that you have had * knowledge of the CeCILL license and that you accept its terms. * */ package hornet.framework.clamav.service; import hornet.framework.clamav.bo.ClamAvResponse; import hornet.framework.clamav.bo.TypeResponse; import hornet.framework.clamav.exception.ClamAVException; import hornet.framework.clamav.pool.PooledSocketFactory; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.RandomAccessFile; import java.net.ConnectException; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.SocketChannel; import java.util.Date; import org.apache.commons.pool2.ObjectPool; import org.apache.commons.pool2.impl.GenericObjectPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Cette classe propose des mthodes pour simplifier les tests antivirus, via ClamAV, sur des fichiers reus * (suite a un upload ou non) * * - par flux TCP : On envoi le fichier directement comme flux TCP. * * @author EffiTIC */ public final class ClamAVCheckService { /** Charset */ private static final String UTF_8 = "UTF-8"; /** Message d'erreur. */ private static final String ERROR_ON_COMMAND = "Erreur lors de l'envoi de la Commande '%s' l'antivirus"; /** Constant for buffer size. */ private static final int BUFFER_SIZE = 1024; /** Code d'erreur technique CLAMAV. */ private static final String ERR_TEC_CLAMAV_01 = "ERR-TEC-CLAMAV-01"; /** Logger de la classe. */ private static final Logger LOGGER = LoggerFactory.getLogger(ClamAVCheckService.class); /** * Dlai par dfaut pour les rponses en millisecondes. */ private static final int DEFAULT_TIMEOUT = 1000; /** * Dlai par dfaut pour les connexions en millisecondes. */ private static final int DEFAULT_TIMEOUT_CONNECT = 1000; /** Port par dfaut. */ private static final int DEFAULT_PORT = 3310; /** Chaine de caractre reprsentant la commande. */ private static final String COMMANDE = "nIDSESSION\nnINSTREAM\n"; /** * Contient l'url d'accs au serveur clamAV. */ private String clamAVServer = ""; /** * Contient le port d'accs au serveur clamAV. */ private int clamAVPort = ClamAVCheckService.DEFAULT_PORT; /** Dlai accord ClamAV pour rpondre en milli secondes. */ private int timeout = ClamAVCheckService.DEFAULT_TIMEOUT; /** Dlai accord ClamAV lors des connexions en milli secondes. */ private int connectTimeout = ClamAVCheckService.DEFAULT_TIMEOUT_CONNECT; /** Le pool de socket. */ private final transient ObjectPool<SocketChannel> socketPool; /** * Constructeur. */ public ClamAVCheckService() { super(); this.socketPool = new GenericObjectPool<SocketChannel>(new PooledSocketFactory()); } /** * Cette mthode retourne la version de ClamAV. * * @return la version de clamAV * @throws ClamAVException * the clam av exception */ public ClamAvResponse checkClamAVVersion() throws ClamAVException { return this.callClamAV(TypeResponse.VERSION, "CLamAV"); } /** * Cette mthode retourne les statistiques courantes du serveur ClamAV. * * @return les statistiques du serveur ClamAV * @throws ClamAVException * the clam av exception */ public ClamAvResponse checkClamAVStats() throws ClamAVException { return this.callClamAV(TypeResponse.STATS, "END"); } /** * Cette mthode permet de tester un fichier par envoi dans un flux TCP du contenu de ce fichier. * * @param fileForTest * - le fichier tester * @return la chaine "OK" si pas de virus, "le nom du virus" en cas de dtection du virus ou la chaine * retourn par clamav en cas d'erreur clamav * @throws ClamAVException * Exception en cas d'erreur */ public ClamAvResponse checkByStream(final File fileForTest) throws ClamAVException { final StringBuilder resultat = new StringBuilder(); ClamAvResponse result = null; // Rcupration de la taille du fichier final long fileForTestSize = fileForTest.length(); SocketChannel channel = null; try { ClamAVCheckService.LOGGER.debug("Fichier a tester : '{}' ({} octets)", fileForTest.getAbsolutePath(), fileForTestSize); // Rcupration du fichier stream final MappedByteBuffer bufFileForTestRead = this.recupFichierStream(fileForTest, fileForTestSize); // Ouverture de la socket channel = this.openSocket(); this.readAndSendFile(resultat, fileForTestSize, channel, bufFileForTestRead); ClamAVCheckService.LOGGER.debug("Retour ClamAV {}", resultat); } catch (final SocketTimeoutException e) { ClamAVCheckService.LOGGER.error(String.format(ERROR_ON_COMMAND, ClamAVCheckService.COMMANDE), e); result = ClamAvResponse.createTimeoutResponse(); } catch (final UnknownHostException e) { ClamAVCheckService.LOGGER.error(String.format(ERROR_ON_COMMAND, ClamAVCheckService.COMMANDE), e); throw new ClamAVException(ERR_TEC_CLAMAV_01, new String[] { e.getMessage() }, e); } catch (final IOException e) { ClamAVCheckService.LOGGER.error(String.format(ERROR_ON_COMMAND, ClamAVCheckService.COMMANDE), e); throw new ClamAVException(ERR_TEC_CLAMAV_01, new String[] { e.getMessage() }, e); } finally { closeSocket(channel); } // ce qui veut dire que l'analyse s'est droule sans erreur if (result == null && resultat.length() > 0) { result = this.traitementReponseClamAV(resultat.toString(), TypeResponse.VIRUS); } return result; } /** * Ouverture de la socket. * * @return la socket * @throws ClamAVException * the clam av exception */ private SocketChannel openSocket() throws ClamAVException { SocketChannel channel = null; try { // Rcuperation de la socket depuis le pool channel = this.socketPool.borrowObject(); if (!channel.isOpen()) { channel = SocketChannel.open(); } if (!channel.isConnected()) { channel.configureBlocking(true); channel.connect(new InetSocketAddress(this.clamAVServer, this.clamAVPort)); } } catch (final Exception e) { ClamAVCheckService.LOGGER.error("Unable to borrow socket from pool", e); throw new ClamAVException(ERR_TEC_CLAMAV_01, new String[] { e.getMessage() }, e); } return channel; } /** * Fermeture de la socket. * * @param channel * la socket */ private void closeSocket(final SocketChannel channel) { try { if (channel != null) { this.socketPool.invalidateObject(channel); } } catch (final IllegalStateException e) { // nothing special to do ClamAVCheckService.LOGGER.debug("Object state", e); } catch (final Exception e) { ClamAVCheckService.LOGGER.error( "Erreur lors de la fermeture de la socket pendant l'envoi de la Commande '{}' l'antivirus", ClamAVCheckService.COMMANDE, e); } } /** * Lecture du fichier et envoi sur la socket. * * @param resultat * resultat * @param fileForTestSize * fileForTestSize * @param channel * channel * @param bufFileForTestRead * bufFileForTestRead * @throws IOException * IOException */ protected void readAndSendFile(final StringBuilder resultat, final long fileForTestSize, final SocketChannel channel, final MappedByteBuffer bufFileForTestRead) throws IOException { // Envoi de la commande final ByteBuffer writeReadBuffer = ByteBuffer.allocate(BUFFER_SIZE); writeReadBuffer.put(ClamAVCheckService.COMMANDE.getBytes(UTF_8)); writeReadBuffer.put(this.intToByteArray((int) fileForTestSize)); writeReadBuffer.flip(); channel.write(writeReadBuffer); // Envoi du fichier long size = fileForTestSize; // envoi du fichier while (size > 0) { size -= channel.write(bufFileForTestRead); } final ByteBuffer writeBuffer = ByteBuffer.allocate(4); writeBuffer.put(new byte[] { 0, 0, 0, 0 }); writeBuffer.flip(); channel.write(writeBuffer); // lecture de la rponse ByteBuffer readBuffer; readBuffer = ByteBuffer.allocate(BUFFER_SIZE); // lecture de la rponse readBuffer.clear(); boolean readLine = false; while (!readLine) { final int numReaden = channel.read(readBuffer); if (numReaden > 0) { readLine = readBuffer.get(numReaden - 1) == '\n'; resultat.append(new String(readBuffer.array(), 0, numReaden, UTF_8)); readBuffer.clear(); } else { if (numReaden == -1) { readLine = true; readBuffer.clear(); } } } } /** * Mthode de traitement de la rponse renvoyer. * * @param resultat * le rsultat ClamAV * @param type * le type de rponse ClamAV * @return le rsultat */ protected ClamAvResponse traitementReponseClamAV(final String resultat, final TypeResponse type) { ClamAvResponse response = null; // traitement du retour de ClamAV switch (type) { case VIRUS: response = ClamAvResponse.createVirusResponse(resultat); break; case STATS: response = ClamAvResponse.createStatsResponse(resultat); break; case VERSION: response = ClamAvResponse.createVersionResponse(resultat); break; default: break; } return response; } /** * Rcupration du fichier stream. * * @param fileForTest * fichier de test * @param fileForTestSize * taille du fichier * @return MappedByteBuffer * @throws IOException * probleme de lecture */ protected MappedByteBuffer recupFichierStream(final File fileForTest, final long fileForTestSize) throws IOException { // Rcupration du fichier stream final RandomAccessFile raf = new RandomAccessFile(fileForTest, "r"); final FileChannel readChannel = raf.getChannel(); MappedByteBuffer bufFile = null; try { bufFile = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileForTestSize); } finally { if (readChannel != null) { try { readChannel.close(); } catch (final IOException e) { ClamAVCheckService.LOGGER.error("Erreur lors de la fermeture de la socket", e); } } if (raf != null) { try { raf.close(); } catch (final IOException e) { ClamAVCheckService.LOGGER.error("Erreur lors de la fermeture de la socket", e); } } } return bufFile; } /** * Cette mthode transforme un integer en un tableau de 4 bytes. * * @param value * 'entier transform * @return le tableau de byte reprsentatnt l'integer non sign */ private byte[] intToByteArray(final int value) { final int shiftForFirstByte = 24; final int shiftForSecondByte = 16; final int shiftForThirdByte = 8; return new byte[] { (byte) (value >>> shiftForFirstByte), (byte) (value >>> shiftForSecondByte), (byte) (value >>> shiftForThirdByte), (byte) value }; } /** * Cette mthode permet l'envoie d'une commande ClamAV et retourne le rsultat sous la forme d'une * chaine de caractre. * * @param command * - la commade transmettre * @param strEndDetection * - la chaine de caractre permettant de dtecter la dernire ligne du retour. * @return La rponse de clamAV sans traitement * @throws ClamAVException * the clam av exception */ private ClamAvResponse callClamAV(final TypeResponse command, final String strEndDetection) throws ClamAVException { ClamAvResponse result; final StringBuilder resultat = new StringBuilder(); final Date dateDebut = new Date(); Socket socket = null; BufferedReader buffer = null; try { socket = this.connect(command); if (socket == null) { result = ClamAvResponse.createNoServiceResponse(); } else { // timeout pour l'ensemble des oprations sur la // socket socket.setSoTimeout(this.timeout); final InputStream input = socket.getInputStream(); final OutputStream ouput = socket.getOutputStream(); // envoi de la commande clamAV ouput.write(("n" + command.toString() + "\n").getBytes(UTF_8)); // Attente et traitement de la rponse buffer = new BufferedReader(new InputStreamReader(input, UTF_8)); String retour = ""; int indexResultat = -1; while (retour != null && indexResultat == -1) { retour = buffer.readLine(); if (retour != null) { indexResultat = retour.indexOf(strEndDetection); resultat.append(retour); resultat.append('\n'); } } ClamAVCheckService.LOGGER.debug("Retour ClamAV (en {} ms) :\n{}", new Date().getTime() - dateDebut.getTime(), resultat); result = this.traitementReponseClamAV(resultat.toString(), command); } } catch (final UnknownHostException e) { ClamAVCheckService.LOGGER.error(String.format(ERROR_ON_COMMAND, command), e); throw new ClamAVException(ERR_TEC_CLAMAV_01, new String[] { e.getMessage() }, e); } catch (final IOException e) { ClamAVCheckService.LOGGER.error(String.format(ERROR_ON_COMMAND, command), e); throw new ClamAVException(ERR_TEC_CLAMAV_01, new String[] { e.getMessage() }, e); } finally { this.safeClose(command, socket, buffer); } return result; } /** * Fermeture scuris de la socket. * * @param command * command * @param socket * socket * @param buffer * buffer */ private void safeClose(final TypeResponse command, final Socket socket, final BufferedReader buffer) { try { if (buffer != null) { buffer.close(); } } catch (final IOException e) { ClamAVCheckService.LOGGER.error( "Problme de fermeture du buffer lors de l'envoi de la commande'{}' l'antivirus", command, e); } try { if (socket != null) { socket.close(); } } catch (final IOException e) { ClamAVCheckService.LOGGER.error( "Problme de fermeture de la socket lors de l'envoi de la commande'{}' l'antivirus", command, e); } } /** * Ouvre la socket avec dlai max. * * @param command * le type de rponse ClamAV * @return null si le dlai imparti la connexion a t atteint * @throws IOException * si erreur autre que timeout atteint */ private Socket connect(final TypeResponse command) throws IOException { Socket socket = null; ClamAVCheckService.LOGGER.debug("Ouverture du socket pour la commande : '{}'", command); final InetSocketAddress hostport = new InetSocketAddress(this.clamAVServer, this.clamAVPort); socket = new Socket(); // connexion avec timeout try { socket.connect(hostport, this.connectTimeout); } catch (final ConnectException e) { ClamAVCheckService.LOGGER.error("Timeout systme lors de la connexion ", e); return null; } catch (final SocketTimeoutException e) { ClamAVCheckService.LOGGER.error("Timeout atteint lors de la connexion ", e); return null; } return socket; } /** * Getter pour clamAVServer. * * @return la valeur de clamAVServer */ public String getClamAVServer() { return this.clamAVServer; } /** * Setter pour clamAVServer. * * @param clamAVServer * le serveur */ public void setClamAVServer(final String clamAVServer) { this.clamAVServer = clamAVServer; } /** * Getter pour clamAVPort. * * @return la valeur de clamAVPort */ public int getClamAVPort() { return this.clamAVPort; } /** * Setter pour clamAVPort. * * @param clamAVPort * la valeur du port */ public void setClamAVPort(final int clamAVPort) { this.clamAVPort = clamAVPort; } /** * Getter pour timeout. * * @return la valeur de timeout */ public int getTimeout() { return this.timeout; } /** * Setter pour timeout. * * @param readTimeout * la valeur du timeout */ public void setTimeout(final int readTimeout) { this.timeout = readTimeout; } /** * Getter pour connectTimeout. * * @return la valeur de connectTimeout */ public int getConnectTimeout() { return this.connectTimeout; } /** * Setter pour connectTimeout. * * @param connectTimeout * connectTimeout */ public void setConnectTimeout(final int connectTimeout) { this.connectTimeout = connectTimeout; } }