ClamD Protocol - ClamAV Documentation
Description: An open source malware detection toolkit and antivirus engine.

## Keyboard shortcuts

Press `←` or `→` to navigate between chapters

Press `S` or `/` to search in the book

Press `?` to show this help

Press `Esc` to hide this help

# ClamD Protocol

Table Of Contents

- ClamD Protocol
  - Overview
  - Socket Types
  - Command Framing
  - Scan Commands
    - SCAN path
    - CONTSCAN path
    - MULTISCAN path
    - ALLMATCHSCAN path
    - INSTREAM
    - FILDES
    - Legacy STREAM
  - Other Commands
    - PING
    - VERSION
    - VERSIONCOMMANDS
    - RELOAD
    - SELFCHECK
    - SHUTDOWN
    - STATS
  - Reading Replies
  - IDSESSION
    - Using IDSESSION for parallel scanning
    - Commands accepted inside IDSESSION
    - IDSESSION replies
    - Avoiding IDSESSION deadlocks
  - Protocol Limits
  - Examples
    - Basic checks
    - INSTREAM example
    - FILDES example
    - IDSESSION example
  - ClamDScan Behavior
    - Mode selection
    - Multiscan behavior
    - Option interactions
      - Transport override options
      - `--fdpass` with TCP sockets
      - Remote TCP scans
      - `--multiscan` and `--allmatch`
      - `--allmatch`
      - `--multiscan` and `--stream`
      - `--multiscan` and `--fdpass`
      - Quarantine actions
      - Quarantine actions with multiscan

## Overview

`clamd` is a long-running scanning daemon. Clients such as `clamdscan`, `clamonacc`, mail filters, and custom integrations connect to a configured socket and send simple command records.

Use `clamdscan` for normal command-line scanning. Use this protocol directly when a custom client needs to choose how to submit work: by daemon-visible path, by streamed file contents, or by passing an already-open file descriptor.

Custom clients should use `VERSIONCOMMANDS` during startup or compatibility checks. It reports the command names recognized by the connected daemon. Some recognized commands may still be disabled by `clamd.conf` or unavailable because the required platform support was not compiled in.

## Socket Types

`clamd` can listen on a Unix/local socket, a TCP socket, or both.

Unix/local sockets:

- Support path-based commands such as `SCAN`, `CONTSCAN`, `MULTISCAN`, and `ALLMATCHSCAN`.
- Support `INSTREAM` for streaming file contents.
- Support `FILDES` when ClamAV is built with file descriptor passing support.
- Are the only socket type that can use `FILDES`.

TCP sockets:

- Support path-based commands if the path is meaningful on the daemon host.
- Support `INSTREAM` for sending file contents over the socket.
- Do not support `FILDES`.
- Are not encrypted or authenticated by `clamd`. Do not expose a TCP socket to untrusted networks.

Path-based scan commands are resolved by `clamd`, not by the client. A path sent over TCP must exist on the system where `clamd` runs and must be accessible to the daemon process. When `clamdscan` connects to a remote daemon over TCP, it streams local file contents with `INSTREAM` instead of sending local client paths for the daemon to open.

## Command Framing

New clients should use explicit command framing. The first byte selects the command terminator:

- `zCOMMAND\0` uses a NUL byte as the command terminator. This is the recommended form because it does not conflict with newlines in command arguments.
- `nCOMMAND\n` uses a newline as the command terminator.

In the examples above, `\0` means a literal NUL byte, and `\n` means a literal newline byte.

`clamd` replies with the same record terminator style selected by the command. For example, `zPING\0` receives a NUL-terminated `PONG` reply, and `nPING\n` receives a newline-terminated `PONG` reply. Some multiline diagnostic replies, notably `STATS`, contain embedded newline bytes and end with a final `END` record.

Legacy unprefixed commands are accepted for some commands outside of `IDSESSION`, but new clients should not use them. Unprefixed commands are not accepted inside `IDSESSION`.

Framed command examples:

    zPING\0
    nVERSIONCOMMANDS\n
    zSCAN /srv/uploads/sample.bin\0

## Scan Commands

### SCAN path

`SCAN path` scans one file or recursively scans one directory. A full path is required. For directory scans, `SCAN` stops after a virus is found.

### CONTSCAN path

`CONTSCAN path` scans one file or recursively scans one directory and continues scanning after detections are found.

### MULTISCAN path

`MULTISCAN path` scans a directory recursively and asks `clamd` to use multiple worker threads for that directory tree. If the target is not a directory, `clamd` falls back to non-multiscan scanning behavior.

`MULTISCAN` is server-side directory parallelism: the client sends one directory path, and the daemon walks and schedules work for that tree.

### ALLMATCHSCAN path

`ALLMATCHSCAN path` is like `SCAN`, but enables all-match behavior so scanning can continue within a file after a match. This command must use `n` or `z` framing.

`ALLMATCHSCAN` can be disabled with `AllowAllMatchScan` in `clamd.conf`. If disabled, `clamd` returns an error for the command.

### INSTREAM

`INSTREAM` sends file contents to `clamd` on the same socket. This command must use `n` or `z` framing.

After the command record, send one or more chunks. Each chunk starts with a 4-byte unsigned length in network byte order, followed by that many bytes of data:

    4-byte big-endian length
    chunk bytes

End the stream with a zero-length chunk:

    00 00 00 00

The total streamed size must not exceed `StreamMaxLength` in `clamd.conf`. If the stream exceeds that limit, `clamd` replies with an `INSTREAM size limit exceeded` error.

### FILDES

`FILDES` passes an already-open file descriptor to `clamd` using Unix socket ancillary data. It only works over Unix/local sockets and only when file descriptor passing support is available.

After the `FILDES` command, send the descriptor using an RFC 2292 / BSD 4.4 style ancillary-data message. The descriptor message must include at least one dummy byte. The descriptor may be sent in the same packet as the command or in a subsequent packet.

### Legacy STREAM

The old `STREAM` command is no longer supported by current `clamd`. Use `INSTREAM` instead.

## Other Commands

### PING

`PING` checks whether `clamd` is responding. A healthy daemon replies with `PONG`.

### VERSION

`VERSION` prints the ClamAV program and database version. It is enabled by default and can be disabled with `EnableVersionCommand` in `clamd.conf`. If disabled, `clamd` replies with `COMMAND UNAVAILABLE`.

### VERSIONCOMMANDS

`VERSIONCOMMANDS` prints version information followed by a `COMMANDS:` list. Use this to discover protocol support for the connected daemon.

`VERSIONCOMMANDS` must use `n` or `z` framing.

### RELOAD

`RELOAD` requests a database reload. It is available only when `EnableReloadCommand` is enabled in `clamd.conf`. If unavailable, `clamd` replies with `COMMAND UNAVAILABLE`.

### SELFCHECK

`SELFCHECK` checks whether the loaded databases are current. It is available only when `EnableSelfCheckCommand` is enabled in `clamd.conf`. If unavailable, `clamd` replies with `COMMAND UNAVAILABLE`.

### SHUTDOWN

`SHUTDOWN` requests a clean daemon exit. It is available only when `EnableShutdownCommand` is enabled in `clamd.conf`. If unavailable, `clamd` replies with `COMMAND UNAVAILABLE`.

### STATS

`STATS` prints queue, task, and memory statistics. It is enabled by default and can be disabled with `EnableStatsCommand` in `clamd.conf`. If disabled, `clamd` replies with `COMMAND UNAVAILABLE`.

The exact `STATS` output format is not stable. Parse it as diagnostic text, not as a fixed schema.

## Reading Replies

For non-session commands, read reply records until `clamd` closes the connection. A scan can produce multiple reply records, especially when scanning a directory.

Common scan replies look like:

    /path/to/file: OK
    /path/to/file: Eicar-Signature FOUND
    /path/to/file: Access denied ERROR
    stream: OK
    stream: Eicar-Signature FOUND

The reply record terminator is selected by the command framing: newline for `n` commands and NUL for `z` commands.

For `STATS`, read until the `END` line. `STATS` output contains newline-delimited diagnostic text even when the command uses `z` framing; the final `END` record uses the command terminator.

## IDSESSION

`IDSESSION` lets a client send multiple requests over one socket. Start the session with `zIDSESSION\0` or `nIDSESSION\n`, then send prefixed commands, and finish with `zEND\0` or `nEND\n`.

Every command inside `IDSESSION`, including `END`, must use `n` or `z` framing. Legacy unprefixed commands are rejected inside a session.

### Using IDSESSION for parallel scanning

`IDSESSION` is the client-driven alternative to directory-based `MULTISCAN` for multithreaded scanning.

`MULTISCAN` asks the daemon to walk one directory tree and parallelize that server-side directory scan. `IDSESSION` lets the client send many per-file requests over one connection so `clamd` can dispatch those requests to worker threads while the client keeps control of path resolution, streaming, descriptor passing, filtering, and result-to-action mapping.

### Commands accepted inside IDSESSION

Accepted inside `IDSESSION`:

- `SCAN`
- `INSTREAM`
- `FILDES`
- `VERSION`
- `VERSIONCOMMANDS`
- `PING`
- `STATS`
- `END`

Not accepted inside `IDSESSION`:

- `CONTSCAN`
- `MULTISCAN`
- `ALLMATCHSCAN`
- `RELOAD`
- `SHUTDOWN`
- `SELFCHECK`

`FILDES` inside `IDSESSION` still requires a Unix/local socket and descriptor passing support. `INSTREAM` inside `IDSESSION` uses the same chunk format as non-session `INSTREAM`.

### IDSESSION replies

Replies inside `IDSESSION` are prefixed with the numeric request ID. IDs start at 1 for the first command after `IDSESSION`; the `IDSESSION` command itself does not consume a visible reply ID.

    1: /path/a: OK
    2: /path/b: Eicar-Signature FOUND
    3: PONG

Requests are processed asynchronously, so replies can arrive out of order. Match replies to requests by numeric ID, not by arrival order.

Avoid mixing `STATS` with other in-flight session work unless the client is prepared to handle multiline diagnostic output.

### Avoiding IDSESSION deadlocks

An `IDSESSION` client must keep reading replies while it sends more commands. The recommended implementation is a nonblocking socket with `select()` or `poll()`: when sending would block, wait until the socket is writable or there are replies to read.

Alternating blocking `send()` and `recv()` calls can deadlock. If `clamd` detects that a client has deadlocked or is not following the session protocol, it may close the connection.

## Protocol Limits

Path command length is limited by the daemon command receive buffer. Current `clamd` allocates `PATH_MAX + 8` bytes for a command record, plus an internal NUL byte, so the complete framed command must fit in that buffer: the `n` or `z` prefix, command name, separating space when present, path argument, and command terminator.

Because `PATH_MAX` is platform dependent, clients should treat path commands as limited to the daemon host’s normal path-length limit. Use `INSTREAM` or `FILDES` when a local client path is too long, ambiguous, or not meaningful on the daemon host.

`INSTREAM` file content is not part of the command-length limit. Each chunk length is a 32-bit unsigned value in network byte order, and the total stream is limited by `StreamMaxLength` in `clamd.conf`. If a chunk would exceed the remaining stream quota, `clamd` replies with an `INSTREAM size limit exceeded` error.

There is no separate configured limit for the total number of requests in one `IDSESSION`. Request IDs are finite implementation integers, so clients should not treat one session as an infinite stream; start a new session for extremely large batches.

In practice, concurrency and queueing are bounded much earlier by `MaxThreads`, `MaxQueue`, file descriptor limits, socket buffers, daemon memory, and timeouts such as `CommandReadTimeout` and `ReadTimeout`. A robust client should cap its own number of in-flight requests and continuously drain replies; it should not enqueue an unbounded tree into one session without backpressure.

## Examples

The following examples use NUL-terminated `z` commands. Replace `/run/clamav/clamd.sock` with the socket configured by `LocalSocket`, or replace the Unix-socket connection code with a TCP socket connection when connecting to `TCPSocket`.

### Basic checks

For quick shell checks against a Unix socket, `nc -U` is convenient:

    printf 'zPING\0' | nc -U /run/clamav/clamd.sock | tr '\0' '\n'
    printf 'zVERSIONCOMMANDS\0' | nc -U /run/clamav/clamd.sock | tr '\0' '\n'
    printf 'zSCAN /srv/uploads/sample.bin\0' | nc -U /run/clamav/clamd.sock | tr '\0' '\n'

For a TCP socket on port 3310:

    printf 'zPING\0' | nc 127.0.0.1 3310 | tr '\0' '\n'

### INSTREAM example

This example sends file contents with `INSTREAM`:

    import socket
    import struct

    socket_path = "/run/clamav/clamd.sock"
    sample_path = "/srv/uploads/sample.bin"

    with open(sample_path, "rb") as sample, socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
        sock.connect(socket_path)
        sock.sendall(b"zINSTREAM\0")
        while True:
            chunk = sample.read(1024 * 1024)
            if not chunk:
                break
            sock.sendall(struct.pack(">I", len(chunk)))
            sock.sendall(chunk)
        sock.sendall(struct.pack(">I", 0))

        reply = b""
        while True:
            data = sock.recv(4096)
            if not data:
                break
            reply += data

    print(reply.replace(b"\0", b"\n").decode("utf-8", "replace"))

### FILDES example

This example sends an open file descriptor with `FILDES`:

    import array
    import os
    import socket

    socket_path = "/run/clamav/clamd.sock"
    sample_path = "/srv/uploads/sample.bin"

    with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
        sock.connect(socket_path)
        sock.sendall(b"zFILDES\0")
        fd = os.open(sample_path, os.O_RDONLY)
        try:
            fds = array.array("i", [fd])
            sock.sendmsg([b"x"], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)])
        finally:
            os.close(fd)

        reply = b""
        while True:
            data = sock.recv(4096)
            if not data:
                break
            reply += data

    print(reply.replace(b"\0", b"\n").decode("utf-8", "replace"))

### IDSESSION example

This example sends a small bounded batch through `IDSESSION`. Clients that send many requests should use nonblocking I/O and read replies while writing new requests.

    import socket

    socket_path = "/run/clamav/clamd.sock"
    paths = ["/srv/uploads/a.bin", "/srv/uploads/b.bin", "/srv/uploads/c.bin"]

    with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
        sock.connect(socket_path)
        sock.sendall(b"zIDSESSION\0")
        for path in paths:
            sock.sendall(("zSCAN " + path).encode("utf-8") + b"\0")
        sock.sendall(b"zPING\0")
        sock.sendall(b"zEND\0")

        replies = b""
        while True:
            data = sock.recv(4096)
            if not data:
                break
            replies += data

    for record in replies.split(b"\0"):
        if record:
            print(record.decode("utf-8", "replace"))

Example `IDSESSION` replies:

    4: PONG
    1: /srv/uploads/a.bin: OK
    2: /srv/uploads/b.bin: Eicar-Signature FOUND
    3: /srv/uploads/c.bin: OK

The reply order above is valid. Clients should match replies to requests with the numeric ID rather than assuming replies arrive in the same order as requests were sent.

## ClamDScan Behavior

`clamdscan` is the standard command-line client for `clamd`. It chooses a scan transport based on the socket type and command-line options, then maps daemon replies back to the paths it reports to the user.

### Mode selection

`clamdscan` chooses one scan transport before sending scan requests:

- Local daemon path mode, no `--stream`, no usable `--fdpass`: path-based scan commands. This can use a Unix/local socket or a TCP socket that `clamdscan` determines is local.
- Unix/local socket with `--fdpass`: `FILDES`.
- Remote TCP socket or `--stream`: `INSTREAM`.
- Standard input: `FILDES` on a local socket when possible, otherwise `INSTREAM`.

### Multiscan behavior

`clamdscan --multiscan` has different meanings depending on the selected transport:

- In local path mode, directory scans use server-side `MULTISCAN`.
- With `--stream` or remote TCP sockets, files are sent through `IDSESSION` with `INSTREAM`.
- With `--fdpass` on a local socket, files are sent through `IDSESSION` with `FILDES`.

The `IDSESSION` forms are client-driven per-file parallelism. They can keep multiple `clamd` worker threads busy, but they are not the same as sending a whole directory to the daemon with `MULTISCAN`.

### Option interactions

The following notes explain how `clamdscan` options change the effective scan mode.

#### Transport override options

`--stream` and `--fdpass` select different ways to submit local files to the daemon. Use one transport override for a scan. `--stream` sends file contents with `INSTREAM`; `--fdpass` passes open files with `FILDES`.

#### `--fdpass` with TCP sockets

`--fdpass` only works with a Unix/local socket and fd-passing support. It is not available over TCP.

#### Remote TCP scans

When `clamdscan` connects to a remote `clamd` over TCP, it streams file contents with `INSTREAM`. It does not send local file paths for the remote daemon to open.

#### `--multiscan` and `--allmatch`

`--allmatch` is not useful with `--multiscan`. If you need all-match behavior, run without `--multiscan`.

#### `--allmatch`

`--allmatch` is only effective for local path-mode scans that are not using `--multiscan`, `--stream`, or `--fdpass`.

#### `--multiscan` and `--stream`

This combination is supported, but it means client-driven `IDSESSION` with `INSTREAM`, not server-side directory `MULTISCAN`.

#### `--multiscan` and `--fdpass`

This combination is supported on Unix/local sockets, but it means client-driven `IDSESSION` with `FILDES`, not server-side directory `MULTISCAN`.

#### Quarantine actions

Choose one quarantine action for a scan: `--move`, `--copy`, or `--remove`.

#### Quarantine actions with multiscan

Quarantine actions such as `--move`, `--copy`, and `--remove` are performed by `clamdscan`, not by `clamd`. For directory scans with quarantine actions enabled, `clamdscan` resolves the action path for each file before requesting the scan. When `--multiscan` is also enabled, `clamdscan` uses client-driven per-file scan requests so it can preserve the scan result to action-path association while still allowing `clamd` to process multiple requests in parallel.