115 lines
3.0 KiB
Go
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])
|
|
}
|
|
}
|
|
}
|