package fi.solita.clamav; /** * Created by dario on 19/10/17. */ import java.io.*; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; /** * Simple client for ClamAV's clamd scanner. Provides straightforward instream scanning. */ public class ClamAVClient { private String hostName; private int port; private int timeout; // "do not exceed StreamMaxLength as defined in clamd.conf, otherwise clamd will reply with INSTREAM size limit exceeded and close the connection." private static final int CHUNK_SIZE = 2048; private static final int DEFAULT_TIMEOUT = 500; private static final int PONG_REPLY_LEN = 4; /** * @param hostName The hostname of the server running clamav-daemon * @param port The port that clamav-daemon listens to(By default it might not listen to a port. Check your clamav configuration). * @param timeout zero means infinite timeout. Not a good idea, but will be accepted. */ public ClamAVClient(String hostName, int port, int timeout) { if (timeout < 0) { throw new IllegalArgumentException("Negative timeout value does not make sense."); } this.hostName = hostName; this.port = port; this.timeout = timeout; } public ClamAVClient(String hostName, int port) { this(hostName, port, DEFAULT_TIMEOUT); } /** * Run PING command to clamd to test it is responding. * * @return true if the server responded with proper ping reply. */ public boolean ping() throws IOException { try (Socket s = new Socket(hostName,port); OutputStream outs = s.getOutputStream()) { s.setSoTimeout(timeout); outs.write(asBytes("zPING\0")); outs.flush(); byte[] b = new byte[PONG_REPLY_LEN]; InputStream inputStream = s.getInputStream(); int copyIndex = 0; int readResult; do { readResult = inputStream.read(b, copyIndex, Math.max(b.length - copyIndex, 0)); copyIndex += readResult; } while (readResult > 0); return Arrays.equals(b, asBytes("PONG")); } } /** * Streams the given data to the server in chunks. The whole data is not kept in memory. * This method is preferred if you don't want to keep the data in memory, for instance by scanning a file on disk. * Since the parameter InputStream is not reset, you can not use the stream afterwards, as it will be left in a EOF-state. * If your goal is to scan some data, and then pass that data further, consider using {@link #scan(byte[]) scan(byte[] in)}. *
* Opens a socket and reads the reply. Parameter input stream is NOT closed.
*
* @param is data to scan. Not closed by this method!
* @return server reply
*/
public byte[] scan(InputStream is) throws IOException {
try (Socket s = new Socket(hostName,port); OutputStream outs = new BufferedOutputStream(s.getOutputStream())) {
s.setSoTimeout(timeout);
// handshake
outs.write(asBytes("zINSTREAM\0"));
outs.flush();
byte[] chunk = new byte[CHUNK_SIZE];
try (InputStream clamIs = s.getInputStream()) {
// send data
int read = is.read(chunk);
while (read >= 0) {
// The format of the chunk is: '