Files
mailgoserver/internal/mailstore/clamav_test.go
T

115 lines
3.0 KiB
Go

package mailstore
import (
"encoding/binary"
"io"
"net"
"testing"
)
// fakeClamd stands in for a real clamd instance: reads the INSTREAM protocol exactly
// as a real one would (command, length-prefixed chunks, zero-length terminator),
// reassembles the full payload so the test can assert on exactly what ScanVirus sent,
// then writes back the given canned reply.
func fakeClamd(t *testing.T, reply string) (addr string, gotPayload chan []byte) {
t.Helper()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { ln.Close() })
gotPayload = make(chan []byte, 1)
go func() {
conn, err := ln.Accept()
if err != nil {
return
}
defer conn.Close()
cmd := make([]byte, len("zINSTREAM\x00"))
if _, err := io.ReadFull(conn, cmd); err != nil {
return
}
var payload []byte
for {
var lenBuf [4]byte
if _, err := io.ReadFull(conn, lenBuf[:]); err != nil {
return
}
n := binary.BigEndian.Uint32(lenBuf[:])
if n == 0 {
break
}
chunk := make([]byte, n)
if _, err := io.ReadFull(conn, chunk); err != nil {
return
}
payload = append(payload, chunk...)
}
gotPayload <- payload
conn.Write([]byte(reply + "\x00"))
}()
return ln.Addr().String(), gotPayload
}
func TestScanVirusCleanReply(t *testing.T) {
addr, gotPayload := fakeClamd(t, "stream: OK")
infected, sig, err := ScanVirus(addr, []byte("hello world"))
if err != nil {
t.Fatal(err)
}
if infected || sig != "" {
t.Fatalf("infected=%v sig=%q, want clean", infected, sig)
}
if got := <-gotPayload; string(got) != "hello world" {
t.Fatalf("clamd received %q, want %q — the length-prefixed chunk protocol is broken", got, "hello world")
}
}
func TestScanVirusInfectedReply(t *testing.T) {
addr, _ := fakeClamd(t, "stream: Eicar-Test-Signature FOUND")
infected, sig, err := ScanVirus(addr, []byte("X5O!P%@AP"))
if err != nil {
t.Fatal(err)
}
if !infected || sig != "Eicar-Test-Signature" {
t.Fatalf("infected=%v sig=%q, want infected with the parsed signature name", infected, sig)
}
}
func TestScanVirusErrorReply(t *testing.T) {
addr, _ := fakeClamd(t, "stream: Access denied ERROR")
if _, _, err := ScanVirus(addr, []byte("x")); err == nil {
t.Fatal("expected an error for a clamd-reported ERROR reply")
}
}
func TestScanVirusUnreachable(t *testing.T) {
if _, _, err := ScanVirus("127.0.0.1:1", []byte("x")); err == nil {
t.Fatal("expected an error dialing an unreachable clamd")
}
}
func TestScanVirusChunksLargePayload(t *testing.T) {
addr, gotPayload := fakeClamd(t, "stream: OK")
large := make([]byte, 20000) // spans multiple 8192-byte chunks
for i := range large {
large[i] = byte(i % 251)
}
if _, _, err := ScanVirus(addr, large); err != nil {
t.Fatal(err)
}
got := <-gotPayload
if len(got) != len(large) {
t.Fatalf("clamd received %d bytes, want %d — multi-chunk reassembly is broken", len(got), len(large))
}
for i := range large {
if got[i] != large[i] {
t.Fatalf("byte %d mismatch: got %d want %d", i, got[i], large[i])
}
}
}