forked from FortnoxAB/goclamd
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclamd.go
More file actions
146 lines (122 loc) · 3.1 KB
/
Copy pathclamd.go
File metadata and controls
146 lines (122 loc) · 3.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package goclamd
import (
"bytes"
"context"
"fmt"
"io"
"regexp"
"time"
)
// EICAR example virus
var EICAR = []byte(`X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*`)
const (
// ResOK resolution OK
ResOK = "OK"
// ResFound resolution virus found
ResFound = "FOUND"
// ResError resolution scan error
ResError = "ERROR"
// ResParseError resolution parse error
ResParseError = "PARSE ERROR"
)
// StreamScanner can be used to stream a file to clamd
type StreamScanner interface {
Scan(io.Reader) error
Ping() error
Reload() error
}
type streamScanner struct {
endpoint string
}
func (s *streamScanner) Scan(r io.Reader) error {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
c, err := DialContext(ctx, s.endpoint)
if err != nil {
return err
}
defer c.Close()
err = c.Command("INSTREAM")
if err != nil {
return err
}
// MAX FILESIZE IS 26213574
//Using io.Copy default buffer which is 32 * 1024
_, err = io.Copy(c, r)
if err != nil {
return CheckResponse(c.ReadResponse())
}
// Empty chunk tells clamd we are done streaming. We can now read the result
_, err = c.Write([]byte{})
if err != nil {
return err
}
return CheckResponse(c.ReadResponse())
}
// Ping pings clamd and returns error if no pong is recieved
func (s *streamScanner) Ping() error {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
c, err := DialContext(ctx, s.endpoint)
if err != nil {
return err
}
defer c.Close()
err = c.SetDeadline(time.Now().Add(time.Second * 2))
if err != nil {
return err
}
err = c.Command("PING")
if err != nil {
return err
}
pong, err := c.ReadResponse()
if string(pong) != "PONG" {
return fmt.Errorf("goclamd: expected PONG, got: %s", string(pong))
}
return err
}
// Reload the virus databases
func (s *streamScanner) Reload() error {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
c, err := DialContext(ctx, s.endpoint)
if err != nil {
return err
}
defer c.Close()
err = c.SetDeadline(time.Now().Add(time.Second * 2))
if err != nil {
return err
}
err = c.Command("RELOAD")
if err != nil {
return err
}
reload, err := c.ReadResponse()
if string(reload) != "RELOADING" {
return fmt.Errorf("goclamd: expected RELOADING, got: %s", string(reload))
}
return err
}
var statusRegexp = regexp.MustCompile(`^(.*): (.*) (FOUND|ERROR|OK)`)
//CheckResponse checks a clamd response for OK or virus found
func CheckResponse(r []byte, err error) error {
if err != nil {
return err
}
if bytes.Equal(r, []byte("stream: OK")) {
return nil
}
match := statusRegexp.FindSubmatch(r)
if len(match) > 3 {
return NewVirusFoundError(match[3], match[2])
}
return fmt.Errorf(string(r))
}
// NewStreamScanner returns a new scanner which uses clamd stream functionality
func NewStreamScanner(endpoint string) StreamScanner {
return &streamScanner{
endpoint: endpoint,
}
}