Crate clamav_client

source ·
Expand description

§Rust ClamAV Client

A simple ClamAV client to send files, in-memory data, and data streams to clamd for antivirus scanning.

It provides a synchronous API as well as asynchronous functions for both Tokio and async-std.

Check out the examples below, the integration tests, or the API docs for more information on how to use this library.

Workflow status

§Installation

Add this to your Cargo.toml:

[dependencies]
clamav-client = "0.5.1"

To use the async functions in clamav_client::tokio, add this to your Cargo.toml:

[dependencies]
clamav-client = { version = "0.5.1", features = ["tokio"] }

To scan Tokio streams, enable the tokio-stream feature instead and add this to your Cargo.toml:

[dependencies]
clamav-client = { version = "0.5.1", features = ["tokio-stream"] }

Support for async-std is also available by enabling the async-std feature:

[dependencies]
clamav-client = { version = "0.5.1", features = ["async-std"] }

§Migrations

§Migrate to 0.5.0

The *_socket and *_tcp functions have been deprecated in favor of more general functions with the same name, but without the suffixes. These updated functions, such as ping, scan_buffer, and scan_file, now have the connection type (TCP or Unix socket) as a parameter, effectively replacing the host_address and socket_path parameters.

For example,

let clamd_host_address = "localhost:3310";
let result = clamav_client::scan_file_tcp("README.md", clamd_host_address, None);
assert!(result.is_ok());

becomes:

let clamd_tcp = clamav_client::Tcp{ host_address: "localhost:3310" };
let result = clamav_client::scan_file("README.md", clamd_tcp, None);
assert!(result.is_ok());

§Examples

§Usage

let clamd_tcp = clamav_client::Tcp{ host_address: "localhost:3310" };

// Ping clamd to make sure the server is available and accepting TCP connections
let clamd_available = match clamav_client::ping(clamd_tcp) {
    Ok(ping_response) => ping_response == clamav_client::PONG,
    Err(_) => false,
};

if !clamd_available {
    println!("Cannot ping clamd at {}", clamd_tcp.host_address);
    return;
}
assert!(clamd_available);

// Scan file for viruses
let file_path = "tests/data/eicar.txt";
let scan_file_response = clamav_client::scan_file(file_path, clamd_tcp, None).unwrap();
let file_clean = clamav_client::clean(&scan_file_response).unwrap();
if file_clean {
    println!("No virus found in {}", file_path);
} else {
    println!("The file {} is infected!", file_path);
}
assert!(!file_clean);

// Scan in-memory data for viruses
let buffer = br#"X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"#;
let scan_buffer_response = clamav_client::scan_buffer(buffer, clamd_tcp, None).unwrap();
let data_clean = clamav_client::clean(&scan_buffer_response).unwrap();
if data_clean {
    println!("No virus found");
} else {
    println!("The data is infected!");
}
assert!(!data_clean);

§Usage - Async with tokio

#[cfg(feature = "tokio-stream")]
tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
    let clamd_tcp = clamav_client::tokio::Tcp{ host_address: "localhost:3310" };

    // Ping clamd asynchronously and await the result
    let clamd_available = match clamav_client::tokio::ping(clamd_tcp).await {
        Ok(ping_response) => ping_response == clamav_client::PONG,
        Err(_) => false,
    };

    if !clamd_available {
        println!("Cannot ping clamd at {}", clamd_tcp.host_address);
        return;
    }
    assert!(clamd_available);

    let file_path = "tests/data/eicar.txt";
    let buffer = br#"X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"#;
    let file = tokio::fs::File::open(file_path).await.unwrap();
    let stream = tokio_util::io::ReaderStream::new(file);

    // Concurrently scan a file, a data buffer, and a file stream for viruses
    let (scan_file_result, scan_buffer_result, scan_stream_result) = tokio::join!(
        clamav_client::tokio::scan_file(file_path, clamd_tcp, None),
        clamav_client::tokio::scan_buffer(buffer, clamd_tcp, None),
        clamav_client::tokio::scan_stream(stream, clamd_tcp, None)
    );

    let scan_file_response = scan_file_result.unwrap();
    let file_clean = clamav_client::clean(&scan_file_response).unwrap();
    if file_clean {
        println!("No virus found in {}", file_path);
    } else {
        println!("The file {} is infected!", file_path);
    }
    assert!(!file_clean);

    let scan_buffer_response = scan_buffer_result.unwrap();
    let data_clean = clamav_client::clean(&scan_buffer_response).unwrap();
    if data_clean {
        println!("No virus found");
    } else {
        println!("The data buffer is infected!");
    }
    assert!(!data_clean);

    let scan_stream_response = scan_stream_result.unwrap();
    let stream_clean = clamav_client::clean(&scan_stream_response).unwrap();
    if stream_clean {
        println!("No virus found");
    } else {
        println!("The file stream is infected!");
    }
    assert!(!stream_clean);
})

§Usage - Async with async-std

#[cfg(feature = "async-std")]
async_std::task::block_on(async {
    let clamd_tcp = clamav_client::async_std::Tcp{ host_address: "localhost:3310" };

    // Ping clamd asynchronously and await the result
    let clamd_available = match clamav_client::async_std::ping(clamd_tcp).await {
        Ok(ping_response) => ping_response == clamav_client::PONG,
        Err(_) => false,
    };

    if !clamd_available {
        println!("Cannot ping clamd at {}", clamd_tcp.host_address);
        return;
    }
    assert!(clamd_available);

    // Scan a file for viruses
    let file_path = "tests/data/eicar.txt";
    let scan_file_result = clamav_client::async_std::scan_file(file_path, clamd_tcp, None).await;
    let scan_file_response = scan_file_result.unwrap();
    let file_clean = clamav_client::clean(&scan_file_response).unwrap();
    if file_clean {
        println!("No virus found in {}", file_path);
    } else {
        println!("The file {} is infected!", file_path);
    }
    assert!(!file_clean);
})

More examples can be found in the tests.

§Development

§Testing locally

For the tests to pass, you should start clamd as follows:

clamd -F --config-file=clamd/clamd.conf

and then run cargo test --all-features to cover all tests.

It doesn’t really matter how you start clamd, as long as the options from clamd.conf are the same for your configuration.

§Contributing

Contributions are welcome!

§Contributors

Modules§

  • Use the feature flag “async-std” to enable this module
  • Use the feature flag “tokio” or “tokio-stream” to enable this module

Structs§

  • Use a Unix socket connection to communicate with a ClamAV server
  • Use a TCP connection to communicate with a ClamAV server

Constants§

  • ClamAV’s response to a PING request

Traits§

Functions§

  • Checks whether the ClamAV response indicates that the scanned content is clean or contains a virus
  • Gets the version number from ClamAV
  • Gets the version number from ClamAV using a Unix socket connection
  • get_version_tcpDeprecated
    Gets the version number from ClamAV using a TCP connection
  • Sends a ping request to ClamAV
  • ping_socketDeprecated
    Sends a ping request to ClamAV using a Unix socket connection
  • ping_tcpDeprecated
    Sends a ping request to ClamAV using a TCP connection
  • Scans a data buffer for viruses
  • Scans a data buffer for viruses using a Unix socket connection
  • scan_buffer_tcpDeprecated
    Scans a data buffer for viruses using a TCP connection
  • Scans a file for viruses
  • Scans a file for viruses using a Unix socket connection
  • scan_file_tcpDeprecated
    Scans a file for viruses using a TCP connection
  • Shuts down a ClamAV server

Type Aliases§