-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclamscan.go
141 lines (114 loc) · 2.76 KB
/
clamscan.go
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
package clamscan
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"os/exec"
"regexp"
"strings"
"time"
)
const (
ClamscanExe = "clamscan"
ClamdscanExe = "clamdscan"
)
var (
virusFound = regexp.MustCompile(`\w+\:\s+(.+)\s+FOUND`)
)
func virusName(stdout string) string {
matches := virusFound.FindStringSubmatch(stdout)
if len(matches) < 2 {
return ""
}
return matches[1]
}
type Engine struct {
exe string
}
type Version struct {
ClamAVVersion string
SignatureVersion string
SignatureDate time.Time
}
func New(exe string) (Engine, error) {
// Systemd will automatically start clamav-daemon through its socket in most
// cases, so there isn't a good (dependency free) way to check if clamdscan
// is running without starting it.
e := Engine{}
path, err := exec.LookPath(exe)
if err != nil {
return e, err
}
// Use full path
e.exe = path
return e, nil
}
// Parses the result of `clamscan --version`, and parses result of the form:
// `0.104.1/26419/Tue Jan 11 01:24:18 2022`
func (e Engine) Version() (Version, error) {
cmd := exec.Command(e.exe, "--version")
out, err := cmd.CombinedOutput()
if err != nil {
return Version{}, err
}
ver := strings.TrimSpace(string(out))
fields := strings.Split(ver, "/")
if len(fields) < 3 {
return Version{}, fmt.Errorf("unexpected output: %s", ver)
}
v := Version{
ClamAVVersion: fields[0],
SignatureVersion: fields[1],
}
date, err := time.Parse("Mon Jan 02 15:05:05 2006", fields[2])
if err != nil {
return v, err
}
v.SignatureDate = date
return v, nil
}
func (e Engine) Scan(file io.Reader) (infected bool, name string, err error) {
return e.ScanContext(context.Background(), file)
}
func (e Engine) ScanContext(ctx context.Context, file io.Reader) (infected bool, name string, err error) {
cmd := exec.CommandContext(ctx, e.exe, "-")
stdin, err := cmd.StdinPipe()
if err != nil {
return false, "", err
}
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
go func() {
defer stdin.Close()
io.Copy(stdin, file)
}()
ec := -1
err = cmd.Run()
if err == nil {
// Everything was OK with the command and return code is 0
return false, "", nil
}
if exitErr, ok := err.(*exec.ExitError); ok {
// Exit codes:
// 0 : No virus found.
// 1 : Virus(es) found.
// 2 : Some error(s) occured.
ec = exitErr.ExitCode()
} else {
// Unknown error occured
return false, "", fmt.Errorf("unknown error: %w", err)
}
if ec == 1 {
// Note, empty virus name is not currently handled.
return true, virusName(stdout.String()), nil
}
// If ec==0, everything was was NOT OK with the command and return code is 0.
if ec == 0 && stdout.Len() == 0 {
return false, "", nil
}
// Consider everything else an error.
return false, "", errors.New(stdout.String())
}