-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added antivirus integration (#171)
- Loading branch information
Showing
83 changed files
with
4,193 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
Foreground yes | ||
|
||
DatabaseDirectory /clamav/db | ||
|
||
TCPSocket 3310 | ||
|
||
MaxScanSize 1024M | ||
MaxFileSize 1024M | ||
StreamMaxLength 1024M | ||
|
||
MaxRecursion 16 | ||
MaxFiles 10000 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
#!/bin/sh | ||
|
||
set -euo pipefail | ||
|
||
mkdir -p /clamav | ||
|
||
envsubst < /etc/clamav/freshclam.conf.tmpl > /etc/clamav/freshclam.conf | ||
envsubst < /etc/clamav/clamd.conf.tmpl > /etc/clamav/clamd.conf | ||
|
||
# we run freshclam first to download the database | ||
freshclam | ||
# we start the freshclam daemon | ||
freshclam -d & | ||
pid1=$! | ||
|
||
# we start the clamd daemon | ||
clamd & | ||
pid2=$! | ||
|
||
# Loop until either process finishes | ||
while true; do | ||
if kill -0 $pid1 >/dev/null 2>&1; then | ||
if kill -0 $pid2 >/dev/null 2>&1; then | ||
sleep 5 | ||
else | ||
kill $pid1 | ||
break | ||
fi | ||
else | ||
kill $pid2 | ||
break | ||
fi | ||
done | ||
|
||
exit 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
DatabaseDirectory /clamav/db | ||
Foreground yes | ||
DatabaseOwner root | ||
DatabaseMirror ${DATABASE_MIRROR:-database.clamav.net} | ||
NotifyClamd yes |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package clamd | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
"net/url" | ||
"time" | ||
) | ||
|
||
const chunkSize = 1024 | ||
|
||
type Client struct { | ||
addr string | ||
} | ||
|
||
func NewClient(addr string) (*Client, error) { | ||
url, err := url.Parse(addr) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse addr: %w", err) | ||
} | ||
|
||
if url.Scheme != "tcp" { | ||
return nil, fmt.Errorf("invalid scheme: %s", url.Scheme) //nolint:goerr113 | ||
} | ||
|
||
return &Client{url.Host}, nil | ||
} | ||
|
||
func (c *Client) Dial() (net.Conn, error) { | ||
conn, err := net.Dial("tcp", c.addr) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to dial: %w", err) | ||
} | ||
|
||
if err := conn.SetDeadline(time.Now().Add(1 * time.Minute)); err != nil { | ||
return nil, fmt.Errorf("failed to set deadline: %w", err) | ||
} | ||
|
||
return conn, nil | ||
} | ||
|
||
func sendCommand(conn net.Conn, command string) error { | ||
if _, err := conn.Write( | ||
[]byte(fmt.Sprintf("n%s\n", command)), | ||
); err != nil { | ||
return fmt.Errorf("failed to write command: %w", err) | ||
} | ||
return nil | ||
} | ||
|
||
func readResponse(conn net.Conn) ([]byte, error) { | ||
buf := make([]byte, 1024) //nolint:gomnd | ||
n, err := conn.Read(buf) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to read response: %w", err) | ||
} | ||
return buf[:n], nil | ||
} | ||
|
||
func sendChunk(conn net.Conn, data []byte) error { | ||
var buf [4]byte | ||
lenData := len(data) | ||
buf[0] = byte(lenData >> 24) //nolint:gomnd | ||
buf[1] = byte(lenData >> 16) //nolint:gomnd | ||
buf[2] = byte(lenData >> 8) //nolint:gomnd | ||
buf[3] = byte(lenData >> 0) | ||
|
||
a := buf | ||
|
||
b := make([]byte, len(a)) | ||
copy(b, a[:]) | ||
|
||
if _, err := conn.Write(b); err != nil { | ||
return fmt.Errorf("failed to write chunk size: %w", err) | ||
} | ||
|
||
if _, err := conn.Write(data); err != nil { | ||
return fmt.Errorf("failed to write chunk: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func sendEOF(conn net.Conn) error { | ||
_, err := conn.Write([]byte{0, 0, 0, 0}) | ||
return err //nolint:wrapcheck | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package clamd | ||
|
||
type VirusFoundError struct { | ||
Name string | ||
} | ||
|
||
func (e *VirusFoundError) Error() string { | ||
return "virus found: " + e.Name | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package clamd | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io" | ||
) | ||
|
||
func (c *Client) InStream(r io.ReaderAt) error { //nolint: cyclop | ||
conn, err := c.Dial() | ||
if err != nil { | ||
return fmt.Errorf("failed to dial: %w", err) | ||
} | ||
defer conn.Close() | ||
|
||
if err := sendCommand(conn, "INSTREAM"); err != nil { | ||
return fmt.Errorf("failed to send INSTREAM command: %w", err) | ||
} | ||
|
||
var iter int64 | ||
for { | ||
buf := make([]byte, chunkSize) | ||
|
||
nr, err := r.ReadAt(buf, iter*chunkSize) | ||
iter++ | ||
|
||
if nr > 0 { | ||
if err := sendChunk(conn, buf[0:nr]); err != nil { | ||
return fmt.Errorf("failed to send chunk: %w", err) | ||
} | ||
} | ||
|
||
if errors.Is(err, io.EOF) { | ||
break | ||
} | ||
if err != nil { | ||
return fmt.Errorf("failed to read chunk: %w", err) | ||
} | ||
} | ||
|
||
if err := sendEOF(conn); err != nil { | ||
return fmt.Errorf("failed to send EOF: %w", err) | ||
} | ||
|
||
response, err := readResponse(conn) | ||
if err != nil { | ||
return fmt.Errorf("failed to read response: %w", err) | ||
} | ||
|
||
if string(response) == "stream: OK\n" { | ||
return nil | ||
} | ||
|
||
return &VirusFoundError{string(response[8 : len(response)-7])} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package clamd_test | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/nhost/hasura-storage/clamd" | ||
) | ||
|
||
func TestClamdInstream(t *testing.T) { | ||
t.Parallel() | ||
|
||
cases := []struct { | ||
name string | ||
filepath string | ||
expectedError error | ||
}{ | ||
{ | ||
name: "clean", | ||
filepath: "clamd.go", | ||
}, | ||
{ | ||
name: "eicarcom2.zip", | ||
filepath: "testdata/eicarcom2.zip", | ||
expectedError: &clamd.VirusFoundError{ | ||
Name: "Win.Test.EICAR_HDB-1", | ||
}, | ||
}, | ||
} | ||
|
||
for _, tc := range cases { | ||
tc := tc | ||
t.Run(tc.name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
client, err := clamd.NewClient("tcp://localhost:3310") | ||
if err != nil { | ||
t.Fatalf("failed to dial: %v", err) | ||
} | ||
|
||
f, err := os.Open(tc.filepath) | ||
if err != nil { | ||
t.Fatalf("failed to open file: %v", err) | ||
} | ||
defer f.Close() | ||
|
||
err = client.InStream(f) | ||
if diff := cmp.Diff(tc.expectedError, err); diff != "" { | ||
t.Errorf("unexpected error (-want +got):\n%s", diff) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package clamd | ||
|
||
import "fmt" | ||
|
||
func (c *Client) Ping() error { | ||
conn, err := c.Dial() | ||
if err != nil { | ||
return fmt.Errorf("failed to dial: %w", err) | ||
} | ||
defer conn.Close() | ||
|
||
if err := sendCommand(conn, "PING"); err != nil { | ||
return fmt.Errorf("failed to send PING command: %w", err) | ||
} | ||
|
||
response, err := readResponse(conn) | ||
if err != nil { | ||
return fmt.Errorf("failed to read response: %w", err) | ||
} | ||
|
||
if string(response) != "PONG\n" { | ||
return fmt.Errorf("unknown response: %s", string(response)) //nolint:goerr113 | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.