mirror of
https://github.com/stoatchat/rust-clamav-client.git
synced 2026-07-01 21:24:06 -04:00
Initial commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "clamav-client"
|
||||
version = "0.1.0"
|
||||
authors = ["Thorsten Blum"]
|
||||
edition = "2018"
|
||||
repository = "https://github.com/toblux/rust-clamav-client"
|
||||
description = "ClamAV client library"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
keywords = ["clamav", "clamd", "anitvirus"]
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Thorsten Blum
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Rust ClamAV Client
|
||||
|
||||
Examples of how to use this ClamAV client library can be found [in the tests](tests/clamav_client.rs).
|
||||
|
||||
Please note:
|
||||
- The functions `ping_socket` and `scan_socket` are only available on UNIX platforms.
|
||||
- The tests only pass if [clamd](https://linux.die.net/man/8/clamd) is running on localhost and accepting TCP connections on port 3310.
|
||||
@@ -0,0 +1 @@
|
||||
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
#[cfg(target_family = "unix")]
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{Read, Result as IoResult, Write},
|
||||
net::{TcpStream, ToSocketAddrs},
|
||||
path::Path,
|
||||
result::Result,
|
||||
str::{from_utf8, Utf8Error},
|
||||
};
|
||||
|
||||
const DEFAULT_CHUNK_SIZE: u32 = 4096; // 4 kibibytes
|
||||
|
||||
fn ping<RW>(mut stream: RW) -> IoResult<Vec<u8>>
|
||||
where
|
||||
RW: Read + Write,
|
||||
{
|
||||
stream.write_all(b"zPING\0")?;
|
||||
|
||||
let capacity = b"PONG\0".len();
|
||||
let mut response = Vec::with_capacity(capacity);
|
||||
stream.read_to_end(&mut response)?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn scan<P, RW>(file_path: P, chunk_size: Option<u32>, mut stream: RW) -> IoResult<Vec<u8>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
RW: Read + Write,
|
||||
{
|
||||
stream.write_all(b"zINSTREAM\0")?;
|
||||
|
||||
let chunk_size = chunk_size.unwrap_or(DEFAULT_CHUNK_SIZE);
|
||||
let mut file = File::open(file_path)?;
|
||||
let mut buffer = vec![0; chunk_size as usize];
|
||||
let mut len;
|
||||
loop {
|
||||
len = file.read(&mut buffer[..])?;
|
||||
if len != 0 {
|
||||
stream.write_all(&(len as u32).to_be_bytes())?;
|
||||
stream.write_all(&buffer[0..len])?;
|
||||
} else {
|
||||
stream.write_all(&[0; 4])?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut response = Vec::new();
|
||||
stream.read_to_end(&mut response)?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
pub fn ping_socket<P>(socket_path: P) -> IoResult<Vec<u8>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let stream = UnixStream::connect(socket_path)?;
|
||||
ping(stream)
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
pub fn scan_socket<P>(file_path: P, socket_path: P, chunk_size: Option<u32>) -> IoResult<Vec<u8>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let stream = UnixStream::connect(socket_path)?;
|
||||
scan(file_path, chunk_size, stream)
|
||||
}
|
||||
|
||||
pub fn ping_tcp<A>(host_address: A) -> IoResult<Vec<u8>>
|
||||
where
|
||||
A: ToSocketAddrs,
|
||||
{
|
||||
let stream = TcpStream::connect(host_address)?;
|
||||
ping(stream)
|
||||
}
|
||||
|
||||
pub fn scan_tcp<P, A>(file_path: P, host_address: A, chunk_size: Option<u32>) -> IoResult<Vec<u8>>
|
||||
where
|
||||
A: ToSocketAddrs,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let stream = TcpStream::connect(host_address)?;
|
||||
scan(file_path, chunk_size, stream)
|
||||
}
|
||||
|
||||
pub fn clean(response: &[u8]) -> Result<bool, Utf8Error> {
|
||||
let response = from_utf8(response)?;
|
||||
Ok(response.contains("OK") && !response.contains("FOUND"))
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
use clamav_client::*;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
const TEST_SOCKET_PATH: &str = "/tmp/clamd.socket";
|
||||
const TEST_HOST_ADDRESS: &str = "localhost:3310";
|
||||
const TEST_FILE_PATH: &str = "eicar.txt";
|
||||
|
||||
const PONG_RESPONSE: &[u8] = b"PONG\0";
|
||||
const EICAR_FOUND_RESPONSE: &[u8] = b"stream: Eicar-Signature FOUND\0";
|
||||
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn test_ping_socket() {
|
||||
let err_msg = format!(
|
||||
"Could not ping clamd via Unix socket at {}",
|
||||
TEST_SOCKET_PATH
|
||||
);
|
||||
let response = ping_socket(TEST_SOCKET_PATH).expect(&err_msg);
|
||||
assert_eq!(&response, PONG_RESPONSE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ping_tcp() {
|
||||
let err_msg = format!("Could not ping clamd via TCP at {}", TEST_HOST_ADDRESS);
|
||||
let response = ping_tcp(TEST_HOST_ADDRESS).expect(&err_msg);
|
||||
assert_eq!(&response, PONG_RESPONSE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn test_scan_socket() {
|
||||
let err_msg = format!(
|
||||
"Could not scan test file {} via socket at {}",
|
||||
TEST_FILE_PATH, TEST_SOCKET_PATH
|
||||
);
|
||||
let response = scan_socket(TEST_FILE_PATH, TEST_SOCKET_PATH, None).expect(&err_msg);
|
||||
assert_eq!(&response, EICAR_FOUND_RESPONSE);
|
||||
|
||||
let is_clean = clean(&response);
|
||||
assert!(matches!(is_clean, Ok(false)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scan_tcp() {
|
||||
let err_msg = format!(
|
||||
"Could not scan test file {} via TCP at {}",
|
||||
TEST_FILE_PATH, TEST_HOST_ADDRESS
|
||||
);
|
||||
let response = scan_tcp(TEST_FILE_PATH, TEST_HOST_ADDRESS, None).expect(&err_msg);
|
||||
assert_eq!(&response, EICAR_FOUND_RESPONSE);
|
||||
|
||||
let is_clean = clean(&response);
|
||||
assert!(matches!(is_clean, Ok(false)));
|
||||
}
|
||||
Reference in New Issue
Block a user