-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e2f96a1
Showing
8 changed files
with
868 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
Simple [ClamAV](http://www.clamav.net/) Java client | ||
|
||
# What is provided | ||
|
||
Support for basic INSTREAM scanning and PING command. | ||
|
||
Clamd protocol is explained here: | ||
http://linux.die.net/man/8/clamd | ||
|
||
# Using the client | ||
|
||
Code is self explanatory. Something like this is the idea: | ||
|
||
``` | ||
ClamAVClient cl = new ClamAVClient("192.168.50.72", 3310); | ||
byte[] reply = cl.scan(input); | ||
if (!ClamAVClient.isCleanReply(reply)) throw new Exception("aaargh"); | ||
``` | ||
|
||
# Creating the jar | ||
|
||
``` | ||
mvn install | ||
``` | ||
|
||
# Testing the client | ||
|
||
To run the automated tests you are assumed to run the clamd in a local virtual machine. | ||
Configuration for [Vagrant](http://www.vagrantup.com/) and [https://www.virtualbox.org/](Oracle Virtualbox) is provided. | ||
|
||
To start test server simply | ||
|
||
``` | ||
cd vagrant | ||
vagrant up clamav | ||
``` | ||
|
||
This will kick up a CentOS virtual machine and install [ClamAV](http://www.clamav.net/) in it. | ||
|
||
# License | ||
|
||
Copyright © 2014 [Solita](http://www.solita.fi) | ||
|
||
Distributed under the GNU Lesser General Public License, either version 2.1 of the License, or | ||
(at your option) any later version. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
#!/bin/bash | ||
set -eu | ||
|
||
# EPEL | ||
rpm -Uvh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm | ||
|
||
# ClamAV | ||
yum install -y clamav clamd | ||
|
||
# take off firewall (local virtual machine - ok) | ||
iptables -F | ||
|
||
# listen to our local IP, not only on localhost | ||
echo 'TCPAddr 192.168.50.72' >> /etc/clamd.conf | ||
|
||
service clamd restart | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
|
||
<modelVersion>4.0.0</modelVersion> | ||
<groupId>fi.solita.clamav</groupId> | ||
<artifactId>clamav-client</artifactId> | ||
<version>1.0.0</version> | ||
<packaging>jar</packaging> | ||
<name>Simple ClamAV client</name> | ||
<description>Simple Java client for using clamd INSTREAM scanning in your application.</description> | ||
<url>https://github.com/solita/clamav-java</url> | ||
<licenses> | ||
<license> | ||
<name>GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1</name> | ||
<url>http://www.gnu.org/licenses/lgpl.txt</url> | ||
</license> | ||
</licenses> | ||
<developers> | ||
<developer> | ||
<name>Antti Virtanen</name> | ||
<email>[email protected]</email> | ||
<organization>Solita</organization> | ||
<organizationUrl>http://www.solita.fi</organizationUrl> | ||
</developer> | ||
</developers> | ||
|
||
<properties> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
</properties> | ||
|
||
<scm> | ||
<connection>scm:git:git://github.com/solita/clamav-java.git</connection> | ||
<developerConnection>scm:git:[email protected]:solita/clamav-java.git</developerConnection> | ||
<url>https://github.com/solita/clamav-java</url> | ||
</scm> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>junit</groupId> | ||
<artifactId>junit</artifactId> | ||
<version>4.11</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<version>2.3.2</version> | ||
<configuration> | ||
<source>1.7</source> | ||
<target>1.7</target> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package fi.solita.clamav; | ||
|
||
import java.io.BufferedOutputStream; | ||
import java.io.ByteArrayInputStream; | ||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.OutputStream; | ||
import java.io.UnsupportedEncodingException; | ||
import java.net.Socket; | ||
import java.nio.ByteBuffer; | ||
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; | ||
|
||
/** | ||
* @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[4]; | ||
s.getInputStream().read(b); | ||
return Arrays.equals(b, asBytes("PONG")); | ||
} | ||
} | ||
|
||
/** | ||
* Streams the given data to the server in chunks. The whole data is not kept in memory. | ||
* <p> | ||
* 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]; | ||
|
||
// send data | ||
int read = is.read(chunk); | ||
while (read >= 0) { | ||
// The format of the chunk is: '<length><data>' where <length> is the size of the following data in bytes expressed as a 4 byte unsigned | ||
// integer in network byte order and <data> is the actual chunk. Streaming is terminated by sending a zero-length chunk. | ||
byte[] chunkSize = ByteBuffer.allocate(4).putInt(read).array(); | ||
outs.write(chunkSize); | ||
outs.write(chunk, 0, read); | ||
read = is.read(chunk); | ||
} | ||
|
||
// terminate scan | ||
outs.write(new byte[]{0,0,0,0}); | ||
outs.flush(); | ||
|
||
// read reply | ||
try (InputStream clamIs = s.getInputStream();) { | ||
return readAll(clamIs); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @param in data to scan | ||
* @return server reply | ||
**/ | ||
public byte[] scan(byte[] in) throws IOException { | ||
ByteArrayInputStream bis = new ByteArrayInputStream(in); | ||
return scan(bis); | ||
} | ||
|
||
/** | ||
* @return true if no virus was found according to the clamd reply message | ||
*/ | ||
public static boolean isCleanReply(byte[] reply) throws UnsupportedEncodingException { | ||
String r = new String(reply, "ASCII"); | ||
return (r.contains("OK") && !r.contains("FOUND")); | ||
} | ||
|
||
// byte conversion based on ASCII character set regardless of the current system locale | ||
private static byte[] asBytes(String s) throws UnsupportedEncodingException { | ||
return s.getBytes("ASCII"); | ||
} | ||
|
||
// reads all available bytes from the stream | ||
private static byte[] readAll(InputStream is) throws IOException { | ||
ByteArrayOutputStream tmp = new ByteArrayOutputStream(); | ||
|
||
byte[] buf = new byte[2000]; | ||
int read = is.read(buf); | ||
while (read > 0) { | ||
tmp.write(buf, 0, read); | ||
read = is.read(buf); | ||
} | ||
return tmp.toByteArray(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package fi.solita.clamav; | ||
|
||
import static org.junit.Assert.assertFalse; | ||
import static org.junit.Assert.assertTrue; | ||
|
||
import java.io.IOException; | ||
import java.net.UnknownHostException; | ||
|
||
import org.junit.Test; | ||
|
||
/** | ||
* These tests assume clamd is running and responding in the virtual machine. | ||
*/ | ||
public class InstreamTests { | ||
|
||
private byte[] scan(byte[] input) throws UnknownHostException, IOException { | ||
ClamAVClient cl = new ClamAVClient("192.168.50.72", 3310); | ||
return cl.scan(input); | ||
} | ||
|
||
@Test | ||
public void testRandomBytes() throws UnknownHostException, IOException { | ||
byte[] r = scan("alsdklaksdla".getBytes("ASCII")); | ||
assertTrue(ClamAVClient.isCleanReply(r)); | ||
} | ||
|
||
@Test | ||
public void testPositive() throws UnknownHostException, IOException { | ||
// http://www.eicar.org/86-0-Intended-use.html | ||
byte[] EICAR = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*".getBytes("ASCII"); | ||
byte[] r = scan(EICAR); | ||
assertFalse(ClamAVClient.isCleanReply(r)); | ||
} | ||
|
||
@Test | ||
public void testEmptyBytes() throws UnknownHostException, IOException { | ||
byte[] r = scan(new byte[]{}); | ||
assertTrue(ClamAVClient.isCleanReply(r)); | ||
} | ||
|
||
@Test | ||
public void testStreamChunkingWorks() throws UnknownHostException, IOException { | ||
byte[] multipleChunks = new byte[50000]; | ||
byte[] r = scan(multipleChunks); | ||
assertTrue(ClamAVClient.isCleanReply(r)); | ||
} | ||
|
||
@Test | ||
public void testChunkLimit() throws UnknownHostException, IOException { | ||
byte[] maximumChunk = new byte[2048]; | ||
byte[] r = scan(maximumChunk); | ||
assertTrue(ClamAVClient.isCleanReply(r)); | ||
} | ||
|
||
@Test | ||
public void testZeroBytes() throws UnknownHostException, IOException { | ||
byte[] r = scan(new byte[]{}); | ||
assertTrue(ClamAVClient.isCleanReply(r)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package fi.solita.clamav; | ||
|
||
import static org.junit.Assert.assertTrue; | ||
|
||
import java.io.IOException; | ||
import java.net.UnknownHostException; | ||
|
||
import org.junit.Test; | ||
|
||
/** | ||
* These tests assume clamd is running and responding in the virtual machine. | ||
*/ | ||
public class PingTest { | ||
|
||
@Test | ||
public void testPingPong() throws UnknownHostException, IOException { | ||
ClamAVClient cl = new ClamAVClient("192.168.50.72", 3310); | ||
assertTrue(cl.ping()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# -*- mode: ruby -*- | ||
# vi: set ft=ruby : | ||
|
||
Vagrant.configure("2") do |config| | ||
|
||
# https://github.com/fgrehm/vagrant-cachier#quick-start | ||
if Vagrant.has_plugin?("vagrant-cachier") | ||
config.cache.scope = :box | ||
config.cache.synced_folder_opts = { | ||
type: :nfs, | ||
mount_options: ['rw', 'vers=3', 'tcp', 'nolock'] | ||
} | ||
end | ||
|
||
vmbox = "CentOS-6.5-x86_64-v20140311.box" | ||
vmbox_url = "http://developer.nrel.gov/downloads/vagrant-boxes/CentOS-6.5-x86_64-v20140311.box" | ||
|
||
|
||
# Test server for ClamAV virus scanner | ||
config.vm.define "clamav" do |clamav| | ||
clamav.vm.box = vmbox | ||
clamav.vm.box_url = vmbox_url | ||
|
||
clamav.vm.synced_folder "../env", "/env" | ||
clamav.vm.provision "shell", inline: "cd /env && ./clamd.sh" | ||
|
||
clamav.vm.network "private_network", ip: "192.168.50.72" | ||
end | ||
end |