-
Notifications
You must be signed in to change notification settings - Fork 12
/
clamav.go
305 lines (261 loc) · 8.75 KB
/
clamav.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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
// Use of this source code is governed by a
// license that can be found in the LICENSE file.
// Package go-clamav is go wrapper for libclamav see https://docs.clamav.net/manual/Development/libclamav.html
package goclamav
/*
#include <clamav.h>
#include <stdlib.h>
*/
import "C"
import (
"errors"
"fmt"
"os"
"sync"
"unsafe"
)
// Callback is used to store the interface passed to ScanFileCb. This
// object is then returned in each ClamAV callback for the duration of the
// file scan
type Callback struct {
sync.Mutex
nextID uintptr
cb map[unsafe.Pointer]interface{}
}
var callbacks = Callback{
cb: map[unsafe.Pointer]interface{}{},
}
func setContext(i interface{}) unsafe.Pointer {
cptr := C.malloc(1)
if cptr == nil {
panic("C malloc")
}
callbacks.Lock()
defer callbacks.Unlock()
callbacks.cb[cptr] = i
return cptr
}
func findContext(key unsafe.Pointer) interface{} {
callbacks.Lock()
defer callbacks.Unlock()
if v, ok := callbacks.cb[key]; ok {
return v
}
return nil
}
func deleteContext(key unsafe.Pointer) error {
callbacks.Lock()
defer callbacks.Unlock()
if _, ok := callbacks.cb[key]; ok {
delete(callbacks.cb, key)
C.free(key)
return nil
}
return errors.New("no context to delete")
}
type Clamav struct {
engine *C.struct_cl_engine
signo uint
options *C.struct_cl_scan_options
}
// Init new clamav instance
func (c *Clamav) Init(options SCAN_OPTIONS) error {
c.engine = (*C.struct_cl_engine)(C.cl_engine_new())
scanOptions := &C.struct_cl_scan_options{
general: C.uint(options.General),
heuristic: C.uint(options.Heuristic),
parse: C.uint(options.Parse),
mail: C.uint(options.Mail),
dev: C.uint(options.Dev),
}
c.options = scanOptions
ret := ErrorCode(C.cl_init(CL_INIT_DEFAULT))
if ret != CL_SUCCESS {
err := Strerr(ret)
return err
}
return nil
}
// Use the CvdVerify to verify a database directly:
// As the comment block explains, this will load-test the database. Be advised
// that for some larger databases, this may use a fair bit system RAM.
func (c *Clamav) CvdVerify(path string) error {
_, err := os.Stat(path)
existed := !os.IsNotExist(err)
if !existed {
err := errors.New(fmt.Sprintf("db %s is not exists!", path))
return err
}
fp := C.CString(path)
defer C.free(unsafe.Pointer(fp))
ret := ErrorCode(C.cl_cvdverify(fp))
if ret != CL_SUCCESS {
err := Strerr(ret)
return err
}
return nil
}
// Load clamav virus database
func (c *Clamav) LoadDB(path string, dbopts uint) (uint, error) {
_, err := os.Stat(path)
existed := !os.IsNotExist(err)
if !existed {
err := errors.New(fmt.Sprintf("db %s is not exists!", path))
return 0, err
}
var signo uint
fp := C.CString(path)
defer C.free(unsafe.Pointer(fp))
ret := ErrorCode(C.cl_load(fp, (*C.struct_cl_engine)(c.engine), (*C.uint)(unsafe.Pointer(&signo)), C.uint(dbopts)))
if ret != CL_SUCCESS {
err := Strerr(ret)
return 0, err
}
return signo, nil
}
// When all required databases are loaded you should prepare the detection engine by calling CompileEngine
func (c *Clamav) CompileEngine() error {
ret := ErrorCode(C.cl_engine_compile((*C.struct_cl_engine)(c.engine)))
if ret != CL_SUCCESS {
err := Strerr(ret)
return err
}
return nil
}
// EngineSetNum sets a number in the specified field of the engine configuration.
// Certain fields accept only 32-bit numbers, silently truncating the higher bits
// of the engine config. See dat.go for more information.
func (c *Clamav) EngineSetNum(field EngineField, num uint64) error {
ret := ErrorCode(C.cl_engine_set_num((*C.struct_cl_engine)(c.engine), C.enum_cl_engine_field(field), C.longlong(num)))
if ret != CL_SUCCESS {
err := Strerr(ret)
return err
}
return nil
}
// EngineGetNum acquires a number from the specified field of the engine configuration. Tests show that
// the ClamAV library will not overflow 32-bit fields, so a GetNum on a 32-bit field can safely be
// cast to uint32.
func (c *Clamav) EngineGetNum(field EngineField) (uint64, error) {
var ret ErrorCode
ne := (*C.struct_cl_engine)(c.engine)
num := uint64(C.cl_engine_get_num(ne, C.enum_cl_engine_field(field), (*C.int)(unsafe.Pointer(&ret))))
if ret != CL_SUCCESS {
err := Strerr(ret)
return num, err
}
return num, nil
}
// Free the memory allocated to clamav instance, Free should be called
// when the engine is no longer in use.
func (c *Clamav) Free() error {
ret := ErrorCode(C.cl_engine_free((*C.struct_cl_engine)(c.engine)))
if ret == CL_SUCCESS {
return nil
}
return Strerr(ret)
}
// ScanMapCB scans custom data
func (c *Clamav) ScanMapCB(fmap *Fmap, fileName string, context interface{}) (uint, string, error) {
var scanned C.ulong
var virusName *C.char
fn := C.CString(fileName)
defer C.free(unsafe.Pointer(fn))
// find where to store the context in our callback map. we do _not_ pass the context to
// C directly because aggressive garbage collection will move it around
ctx := setContext(context)
// cleanup
defer deleteContext(ctx)
ret := ErrorCode(C.cl_scanmap_callback((*C.cl_fmap_t)(fmap), fn, &virusName, &scanned, (*C.struct_cl_engine)(c.engine), (*C.struct_cl_scan_options)(c.options), unsafe.Pointer(ctx)))
defer CloseMemory(fmap)
// clean
if ret == CL_SUCCESS {
return uint(scanned), "", nil
}
// virus
if ret == CL_VIRUS {
return uint(scanned), C.GoString(virusName), Strerr(ret)
}
// error
return 0, "", Strerr(ret)
}
// ScanFile scans a single file for viruses using the ClamAV databases. It returns the number of bytes
// read from the file (if found), the virus name and an error code.
// If the file is clean, the virus name is empty and the error code is nil,but if the file is insecure, the
// error code is "Virus(es) detected" and virus name is the matching rule.
func (c *Clamav) ScanFile(path string) (uint, string, error) {
fp := C.CString(path)
defer C.free(unsafe.Pointer(fp))
var virusName *C.char
var scanned C.ulong
ret := ErrorCode(C.cl_scanfile(fp, &virusName, &scanned, (*C.struct_cl_engine)(c.engine), (*C.struct_cl_scan_options)(c.options)))
// clean
if ret == CL_SUCCESS {
return uint(scanned), "", nil
}
// virus
if ret == CL_VIRUS {
return uint(scanned), C.GoString(virusName), Strerr(ret)
}
// error
return 0, "", Strerr(ret)
}
// ScanFileCB scans a single file for viruses using the ClamAV databases and using callbacks from
// ClamAV to read/resolve file data. The callbacks can be used to scan files in memory, to scan multiple
// files inside archives, etc. The function returns the number of bytes
// read from the file (if found), the virus name and an error code.
// If the file is clean, the virus name is empty and the error code is nil,but if the file is insecure, the
// error code is "Virus(es) detected" and virus name is the matching rule.
// The context argument will be sent back to the callbacks, so effort must be made to retain it
// throughout the execution of the scan from garbage collection
func (c *Clamav) ScanFileCB(path string, context interface{}) (uint, string, error) {
fp := C.CString(path)
defer C.free(unsafe.Pointer(fp))
// find where to store the context in our callback map. we do _not_ pass the context to
// C directly because aggressive garbage collection will move it around
ctx := setContext(context)
// cleanup
defer deleteContext(ctx)
var virusName *C.char
var scanned C.ulong
ret := ErrorCode(C.cl_scanfile_callback(fp, &virusName, &scanned, (*C.struct_cl_engine)(c.engine), (*C.struct_cl_scan_options)(c.options), ctx))
// clean
if ret == CL_SUCCESS {
return 0, "", nil
}
// virus
if ret == CL_VIRUS {
return uint(scanned), C.GoString(virusName), Strerr(ret)
}
// error
return 0, "", Strerr(ret)
}
// ScanDesc scans a file descriptor for viruses using the ClamAV databases. It returns the number of bytes
// read from the file (if found), the virus name and an error code.
// If the file is clean, the virus name is empty and the error code is nil,but if the file is insecure, the
// error code is "Virus(es) detected" and virus name is the matching rule.
func (c *Clamav) ScanDesc(desc int32, fileName string) (uint, string, error) {
var scanned C.ulong
var virusName *C.char
fn := C.CString(fileName)
defer C.free(unsafe.Pointer(fn))
ret := ErrorCode(C.cl_scandesc(C.int(desc), fn, &virusName, &scanned, (*C.struct_cl_engine)(c.engine), (*C.struct_cl_scan_options)(c.options)))
// clean
if ret == CL_SUCCESS {
return 0, "", nil
}
// virus
if ret == CL_VIRUS {
return uint(scanned), C.GoString(virusName), Strerr(ret)
}
// error
return 0, "", Strerr(ret)
}
// OpenMemory creates an object from the given memory that can be scanned using ScanMapCb
func OpenMemory(start []byte) *Fmap {
return (*Fmap)(C.cl_fmap_open_memory(unsafe.Pointer(&start[0]), C.size_t(len(start))))
}
// CloseMemory destroys the fmap associated with an in-memory object
func CloseMemory(f *Fmap) {
C.cl_fmap_close((*C.cl_fmap_t)(f))
}