All Downloads are FREE. Search and download functionalities are using the official Maven repository.

clamavj.ClamScan Maven / Gradle / Ivy

There is a newer version: 4.3.1
Show newest version
package clamavj;


import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ClamScan {

    private static Logger log = LoggerFactory.getLogger(ClamScan.class);

    public static final int CHUNK_SIZE = 2048;
    private static final byte[] INSTREAM = "zINSTREAM\0".getBytes();
    private static final byte[] PING = "zPING\0".getBytes();
    private static final byte[] STATS = "nSTATS\n".getBytes();
    // TODO: IDSESSION, END

    //    It is mandatory to prefix this command with n or z, and all commands inside IDSESSION must  be
    //    prefixed.
    //
    //    Start/end  a  clamd  session. Within a session multiple SCAN, INSTREAM, FILDES, VERSION, STATS
    //    commands can be sent on the same socket without opening new connections.  Replies  from  clamd
    //    will  be  in  the form ': ' where  is the request number (in ascii, starting
    //    from 1) and  is the usual clamd reply.  The reply lines have same delimiter  as  the
    //    corresponding  command had.  Clamd will process the commands asynchronously, and reply as soon
    //    as it has finished processing.
    //
    //    Clamd requires clients to read all the replies it sent, before sending more commands  to  pre-vent prevent
    //    vent  send()  deadlocks. The recommended way to implement a client that uses IDSESSION is with
    //    non-blocking sockets, and  a  select()/poll()  loop:  whenever  send  would  block,  sleep  in
    //    select/poll  until either you can write more data, or read more replies.  Note that using non-blocking nonblocking
    //    blocking sockets without the select/poll loop and  alternating  recv()/send()  doesn't  comply
    //    with clamd's requirements.
    //
    //    If  clamd detects that a client has deadlocked,  it will close the connection. Note that clamd
    //    may close an IDSESSION connection too if you don't follow the protocol's requirements.

    private int timeout;
    private String host;
    private int port;

    public ClamScan() {

    }

    public ClamScan(String host, int port, int timeout) {
        setHost(host);
        setPort(port);
        setTimeout(timeout);
    }

    public String stats() {
        return cmd(STATS);
    }

    public boolean ping() {
        return "PONG\0".equals(cmd(PING));
    }

    public String cmd(byte[] cmd) {

        Socket socket = new Socket();

        try {
            socket.connect(new InetSocketAddress(getHost(), getPort()));
        } catch (IOException e) {
            log.error("could not connect to clamd server", e);
            return null;
        }

        try {
            socket.setSoTimeout(getTimeout());
        } catch (SocketException e) {
            log.error("Could not set socket timeout to " + getTimeout() + "ms", e);
        }

        DataOutputStream dos = null;
        StringBuilder response = new StringBuilder();
        try {  // finally to close resources

            try {
                dos = new DataOutputStream(socket.getOutputStream());
            } catch (IOException e) {
                log.error("could not open socket OutputStream", e);
                return null;
            }

            try {
                dos.write(cmd);
                dos.flush();
            } catch (IOException e) {
                log.debug("error writing " + new String(cmd) + " command", e);
                return null;
            }

            InputStream is;
            try {
                is = socket.getInputStream();
            } catch (IOException e) {
                log.error("error getting InputStream from socket", e);
                return null;
            }

            int read = CHUNK_SIZE;
            byte[] buffer = new byte[CHUNK_SIZE];

            while (read == CHUNK_SIZE) {
                try {
                    read = is.read(buffer);
                } catch (IOException e) {
                    log.error("error reading result from socket", e);
                    break;
                }
                response.append(new String(buffer, 0, read));
            }

        } finally {
            if (dos != null) try {
                dos.close();
            } catch (IOException e) {
                log.debug("exception closing DOS", e);
            }
            try {
                socket.close();
            } catch (IOException e) {
                log.debug("exception closing socket", e);
            }
        }

        if (log.isDebugEnabled()) log.debug("Response: " + response.toString());

        return response.toString();
    }

    /**
     * The method to call if you already have the content to scan in-memory as a byte array.
     *
     * @param in the byte array to scan
     * @return the result of the scan
     * @throws IOException
     */
    public ScanResult scan(byte[] in) throws IOException {
        return scan(new ByteArrayInputStream(in));
    }

    /**
     * The preferred method to call. This streams the contents of the InputStream to clamd, so
     * the entire content is not loaded into memory at the same time.
     *
     * @param in the InputStream to read.  The stream is NOT closed by this method.
     * @return a ScanResult representing the server response
     */
    public ScanResult scan(InputStream in) {

        Socket socket = new Socket();

        try {
            socket.connect(new InetSocketAddress(getHost(), getPort()));
        } catch (IOException e) {
            log.error("could not connect to clamd server", e);
            return new ScanResult(e);
        }

        try {
            socket.setSoTimeout(getTimeout());
        } catch (SocketException e) {
            log.error("Could not set socket timeout to " + getTimeout() + "ms", e);
        }

        DataOutputStream dos = null;
        String response = "";
        try {  // finally to close resources

            try {
                dos = new DataOutputStream(socket.getOutputStream());
            } catch (IOException e) {
                log.error("could not open socket OutputStream", e);
                return new ScanResult(e);
            }

            try {
                dos.write(INSTREAM);
            } catch (IOException e) {
                log.debug("error writing INSTREAM command", e);
                return new ScanResult(e);
            }

            int read = CHUNK_SIZE;
            byte[] buffer = new byte[CHUNK_SIZE];
            while (read == CHUNK_SIZE) {
                try {
                    read = in.read(buffer);
                } catch (IOException e) {
                    log.debug("error reading from InputStream", e);
                    return new ScanResult(e);
                }

                if (read > 0) { // if previous read exhausted the stream
                    try {
                        dos.writeInt(read);
                        dos.write(buffer, 0, read);
                    } catch (IOException e) {
                        log.debug("error writing data to socket", e);
                        break;
                    }
                }
            }

            try {
                dos.writeInt(0);
                dos.flush();
            } catch (IOException e) {
                log.debug("error writing zero-length chunk to socket", e);
            }

            try {
                read = socket.getInputStream().read(buffer);
            } catch (IOException e) {
                log.debug("error reading result from socket", e);
                read = 0;
            }

            if (read > 0) response = new String(buffer, 0, read);

        } finally {
            if (dos != null) try {
                dos.close();
            } catch (IOException e) {
                log.debug("exception closing DOS", e);
            }
            try {
                socket.close();
            } catch (IOException e) {
                log.debug("exception closing socket", e);
            }
        }

        if (log.isDebugEnabled()) log.debug("Response: " + response);

        return new ScanResult(response.trim());
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public int getTimeout() {
        return timeout;
    }

    /**
     * Socket timeout in milliseconds
     *
     * @param timeout socket timeout in milliseconds
     */
    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy