forked from mirtchovski/clamav
/
clamav.go
391 lines (344 loc) · 12.2 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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
// Copyright 2013 the Go ClamAV authors
// Use of this source code is governed by a
// license that can be found in the LICENSE file.
// Clamav is a wrapper around libclamav.
// For more information about libclamav see http://www.clamav.net
package clamav
/*
#cgo darwin CPPFLAGS:-Wno-incompatible-pointer-types-discards-qualifiers
#cgo LDFLAGS:-L/usr/local/lib/x86_64 -lclamav
#include <clamav.h>
#include <stdlib.h>
char *fixup_clam_virus(char **name) {
// this undocumented lovelyness brought to you by clamscan, including the comments
char **nvirpp = (const char **)*name; // allmatch needs an extra dereference
return nvirpp[0]; // this is the first virus
}
*/
import "C"
import (
"fmt"
"sync"
"unsafe"
)
var initOnce sync.Once
// 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[uintptr]interface{}
}
var callbacks = Callback{
cb: map[uintptr]interface{}{},
}
// Init initializes the ClamAV library. A suitable initialization can be
// achieved by passing clamav.InitDefault to this function.
func Init(flags uint) error {
var onceerr error
initOnce.Do(func() {
err := ErrorCode(C.cl_init(C.uint(flags)))
if err != Success {
onceerr = fmt.Errorf("Init: %v", StrError(err))
return
}
InitCrypto()
})
return onceerr
}
// InitCrypto initializes the crypto subsystem
func InitCrypto() {
C.cl_initialize_crypto()
}
// DeinitCrypto cleans up the crypto subsystem prior to program exit
func DeinitCrypto() {
C.cl_cleanup_crypto()
}
// New allocates a new ClamAV engine.
func New() *Engine {
eng := (*Engine)(C.cl_engine_new())
return eng
}
// Free relleases the memory associated with ClamAV. Since the ClamAV
// engine can consume several megabytes of memory which is not visible
// by the Go garbage collector, Free should be called when the engine is no
// longer in use.
func (e *Engine) Free() int {
return int(C.cl_engine_free((*C.struct_cl_engine)(e)))
}
// SetNum 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 (e *Engine) SetNum(field EngineField, num uint64) error {
err := C.cl_engine_set_num((*C.struct_cl_engine)(e), C.enum_cl_engine_field(field), C.longlong(num))
if ErrorCode(err) != Success {
return fmt.Errorf("%v", StrError(ErrorCode(err)))
}
return nil
}
// GetNum 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 (e *Engine) GetNum(field EngineField) (uint64, error) {
var err ErrorCode
ne := (*C.struct_cl_engine)(e)
num := uint64(C.cl_engine_get_num(ne, C.enum_cl_engine_field(field), (*C.int)(unsafe.Pointer(&err))))
if err != Success {
return num, fmt.Errorf("%v", StrError(ErrorCode(err)))
}
return num, nil
}
// SetString sets a string in the corresponding field of the engine configuration.
// See dat.go for the corresponding (char *) fields in ClamAV.
func (e *Engine) SetString(field EngineField, s string) error {
str := C.CString(s)
defer C.free(unsafe.Pointer(str))
err := C.cl_engine_set_str((*C.struct_cl_engine)(e), C.enum_cl_engine_field(field), str)
if ErrorCode(err) != Success {
return fmt.Errorf("%v", StrError(ErrorCode(err)))
}
return nil
}
// GetString returns a string from the corresponding field of the engine configuration.
func (e *Engine) GetString(field EngineField) (string, error) {
var err ErrorCode
str := C.GoString(C.cl_engine_get_str((*C.struct_cl_engine)(e), C.enum_cl_engine_field(field), (*C.int)(unsafe.Pointer(&err))))
if err != Success {
return "", fmt.Errorf("%v", StrError(ErrorCode(err)))
}
return str, nil
}
func (e *Engine) CopySettings() *Settings {
return (*Settings)(C.cl_engine_settings_copy((*C.struct_cl_engine)(e)))
}
func (e *Engine) ApplySettings(s *Settings) error {
err := ErrorCode(C.cl_engine_settings_apply((*C.struct_cl_engine)(e), (*C.struct_cl_settings)(s)))
if err != Success {
return fmt.Errorf("%v", StrError(err))
}
return nil
}
func FreeSettings(s *Settings) error {
err := ErrorCode(C.cl_engine_settings_free((*C.struct_cl_settings)(s)))
if err != Success {
return fmt.Errorf("%v", StrError(err))
}
return nil
}
func (e *Engine) Compile() error {
err := ErrorCode(C.cl_engine_compile((*C.struct_cl_engine)(e)))
if err != Success {
return fmt.Errorf("%v", StrError(err))
}
return nil
}
func (e *Engine) Addref() error {
err := ErrorCode(C.cl_engine_compile((*C.struct_cl_engine)(e)))
if err != Success {
return fmt.Errorf("%v", StrError(err))
}
return nil
}
func (e *Engine) ScanDesc(desc int, opts uint) (string, uint, error) {
var name *C.char
var scanned C.ulong
err := ErrorCode(C.cl_scandesc(C.int(desc), &name, &scanned, (*C.struct_cl_engine)(e), C.uint(opts)))
if err == Success {
return "", 0, nil
}
if err == Virus {
return C.GoString(name), uint(scanned), fmt.Errorf(StrError(err))
}
return "", 0, fmt.Errorf(StrError(err))
}
// ScanFile scans a single file for viruses using the ClamAV databases. It returns the virus name
// (if found), the number of bytes read from the file, in CountPrecision units, and a status code.
// If the file is clean the error code will be Success (Clean) and virus name will be empty. If a
// virus is found the error code will be the corresponding string for Virus (currently "Virus(es)
// detected").
func (e *Engine) ScanFile(path string, opts uint) (string, uint, error) {
var name *C.char
var scanned C.ulong
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
err := ErrorCode(C.cl_scanfile(cpath, &name, &scanned, (*C.struct_cl_engine)(e), C.uint(opts)))
if err == Success {
return "", 0, nil
}
if err == Virus {
if opts&ScanAllmatches > 0 {
return C.GoString(C.fixup_clam_virus(&name)), uint(scanned), fmt.Errorf(StrError(err))
} else {
return C.GoString(name), uint(scanned), fmt.Errorf(StrError(err))
}
}
return "", 0, fmt.Errorf(StrError(err))
}
// 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 virus name
// (if found), the number of bytes read from the file in CountPrecision units, and a status code.
// If the file is clean the error code will be Success (Clean) and virus name will be empty. If a
// virus is found the error code will be the corresponding string for Virus (currently "Virus(es)
// detected").
// 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 (e *Engine) ScanFileCb(path string, opts uint, context interface{}) (string, uint, error) {
var name *C.char
var scanned C.ulong
// pass a C-allocated pointer to the path to avoid crashing with garbage collector
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
// 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
callbacks.Lock()
// find the next available empty spot. uintptr overflow should be ok -- we don't expect
// to have Max(uintptr) files scanned at the same time
for _, ok := callbacks.cb[callbacks.nextId]; ok; callbacks.nextId++ {
}
cbidx := callbacks.nextId
callbacks.cb[cbidx] = context
callbacks.nextId++
callbacks.Unlock()
// cleanup
defer func() {
callbacks.Lock()
delete(callbacks.cb, cbidx)
callbacks.Unlock()
}()
err := ErrorCode(C.cl_scanfile_callback(cpath, &name, &scanned, (*C.struct_cl_engine)(e), C.uint(opts), unsafe.Pointer(cbidx)))
if err == Success {
return "", 0, nil
}
if err == Virus {
if opts&ScanAllmatches > 0 {
return C.GoString(C.fixup_clam_virus(&name)), uint(scanned), fmt.Errorf(StrError(err))
} else {
return C.GoString(name), uint(scanned), fmt.Errorf(StrError(err))
}
}
return "", 0, fmt.Errorf(StrError(err))
}
// 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))
}
// ScanMapCb scans custom data
func (e *Engine) ScanMapCb(fmap *Fmap, opts uint, context interface{}) (string, uint, error) {
var name *C.char
var scanned C.ulong
// 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
callbacks.Lock()
// find the next available empty spot. uintptr overflow should be ok -- we don't expect
// to have Max(uintptr) files scanned at the same time
for _, ok := callbacks.cb[callbacks.nextId]; ok; callbacks.nextId++ {
}
cbidx := callbacks.nextId
callbacks.cb[cbidx] = context
callbacks.nextId++
callbacks.Unlock()
// cleanup
defer func() {
callbacks.Lock()
delete(callbacks.cb, cbidx)
callbacks.Unlock()
}()
err := ErrorCode(C.cl_scanmap_callback((*C.cl_fmap_t)(fmap), &name, &scanned, (*C.struct_cl_engine)(e), C.uint(opts), unsafe.Pointer(cbidx)))
if err == Success {
return "", 0, nil
}
if err == Virus {
return C.GoString(name), uint(scanned), fmt.Errorf(StrError(err))
}
return "", 0, fmt.Errorf(StrError(err))
}
// LoadDB loads a single database file or all databases depending on whether its first argument
// (path) points to a file or a directory. A number of loaded signatures will be added to signo
// (the virus counter should be initialized to zero initially)
func (e *Engine) Load(path string, dbopts uint) (uint, error) {
var signo uint
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
err := ErrorCode(C.cl_load(cpath, (*C.struct_cl_engine)(e), (*C.uint)(unsafe.Pointer(&signo)), C.uint(dbopts)))
if err != Success {
return 0, fmt.Errorf("Load: %v", StrError(err))
}
return signo, nil
}
// DBDir returns the directory where the virus database is located
func DBDir() string {
return C.GoString(C.cl_retdbdir())
}
// StatIniDir initializes the Stat structure so the internal state of the database
// can be checked for errors stat should not be reused across calls to Stat*
func StatIniDir(dir string, stat *Stat) error {
p := C.CString(dir)
defer C.free(unsafe.Pointer(p))
err := ErrorCode(C.cl_statinidir(p, (*C.struct_cl_stat)(stat)))
if err != Success {
return fmt.Errorf("StatIniDir: %v", StrError(err))
}
return nil
}
// StatChkDir returns 0 if no change to the directory pointed to by the Stat structure
// occurred, or 1 if some change occurred.
func StatChkDir(stat *Stat) bool {
t := C.cl_statchkdir((*C.struct_cl_stat)(stat))
if t == 1 {
return true
}
return false
}
func StatFree(stat *Stat) error {
err := ErrorCode(C.cl_statfree((*C.struct_cl_stat)(stat)))
if err != Success {
return fmt.Errorf("StatFree: %v", StrError(err))
}
return nil
}
// StatChkReload updates the internal state of the database if a change in the path
// referenced by stat occurred
func StatChkReload(stat *Stat) (bool, error) {
if StatChkDir(stat) {
StatFree(stat)
stat = new(Stat)
return true, StatIniDir(DBDir(), stat)
}
return false, nil
}
// CountSigs counts the number of signatures that can be loaded from
// the directory in path.
func CountSigs(path string, options uint) (uint, error) {
var cnt uint
p := C.CString(path)
defer C.free(unsafe.Pointer(p))
err := ErrorCode(C.cl_countsigs(p, C.uint(options), (*C.uint)(unsafe.Pointer(&cnt))))
if err != Success {
return 0, fmt.Errorf("CountSigs: %v", StrError(err))
}
return cnt, nil
}
// Debug enables debug messages from libclamav
func Debug() {
C.cl_debug()
}
func Retflevel() uint {
return uint(C.cl_retflevel())
}
func Retver() string {
return C.GoString(C.cl_retver())
}
// Strerror converts LibClam error codes to human readable format
func StrError(errno ErrorCode) string {
return errno.String()
}
// String converts the error code to human readable format
func (e ErrorCode) String() string {
return C.GoString(C.cl_strerror(C.int(e)))
}