Parse Result instead of only returning a string
This commit is contained in:
63
clamd.go
63
clamd.go
@@ -33,6 +33,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
RES_OK = "OK"
|
||||||
|
RES_FOUND = "FOUND"
|
||||||
|
RES_ERROR = "ERROR"
|
||||||
|
RES_PARSE_ERROR = "PARSE ERROR"
|
||||||
|
)
|
||||||
|
|
||||||
type Clamd struct {
|
type Clamd struct {
|
||||||
address string
|
address string
|
||||||
}
|
}
|
||||||
@@ -45,6 +52,15 @@ type Stats struct {
|
|||||||
Queue string
|
Queue string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ScanResult struct {
|
||||||
|
Raw string
|
||||||
|
Description string
|
||||||
|
Path string
|
||||||
|
Hash string
|
||||||
|
Size int
|
||||||
|
Status string
|
||||||
|
}
|
||||||
|
|
||||||
var EICAR = []byte(`X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*`)
|
var EICAR = []byte(`X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*`)
|
||||||
|
|
||||||
func (c *Clamd) newConnection() (conn *CLAMDConn, err error) {
|
func (c *Clamd) newConnection() (conn *CLAMDConn, err error) {
|
||||||
@@ -67,14 +83,12 @@ func (c *Clamd) newConnection() (conn *CLAMDConn, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Clamd) simpleCommand(command string) (chan string, error) {
|
func (c *Clamd) simpleCommand(command string) (chan *ScanResult, error) {
|
||||||
conn, err := c.newConnection()
|
conn, err := c.newConnection()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// defer conn.Close()
|
|
||||||
|
|
||||||
err = conn.sendCommand(command)
|
err = conn.sendCommand(command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -83,10 +97,7 @@ func (c *Clamd) simpleCommand(command string) (chan string, error) {
|
|||||||
ch, wg, err := conn.readResponse()
|
ch, wg, err := conn.readResponse()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
// wait for waitgroup
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
// close connection
|
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -104,7 +115,7 @@ func (c *Clamd) Ping() error {
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case s := (<-ch):
|
case s := (<-ch):
|
||||||
switch s {
|
switch s.Raw {
|
||||||
case "PONG":
|
case "PONG":
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
@@ -118,7 +129,7 @@ func (c *Clamd) Ping() error {
|
|||||||
/*
|
/*
|
||||||
Print program and database versions.
|
Print program and database versions.
|
||||||
*/
|
*/
|
||||||
func (c *Clamd) Version() (chan string, error) {
|
func (c *Clamd) Version() (chan *ScanResult, error) {
|
||||||
dataArrays, err := c.simpleCommand("VERSION")
|
dataArrays, err := c.simpleCommand("VERSION")
|
||||||
return dataArrays, err
|
return dataArrays, err
|
||||||
}
|
}
|
||||||
@@ -137,17 +148,17 @@ func (c *Clamd) Stats() (*Stats, error) {
|
|||||||
stats := &Stats{}
|
stats := &Stats{}
|
||||||
|
|
||||||
for s := range ch {
|
for s := range ch {
|
||||||
if strings.HasPrefix(s, "POOLS") {
|
if strings.HasPrefix(s.Raw, "POOLS") {
|
||||||
stats.Pools = strings.Trim(s[6:], " ")
|
stats.Pools = strings.Trim(s.Raw[6:], " ")
|
||||||
} else if strings.HasPrefix(s, "STATE") {
|
} else if strings.HasPrefix(s.Raw, "STATE") {
|
||||||
stats.State = s
|
stats.State = s.Raw
|
||||||
} else if strings.HasPrefix(s, "THREADS") {
|
} else if strings.HasPrefix(s.Raw, "THREADS") {
|
||||||
stats.Threads = s
|
stats.Threads = s.Raw
|
||||||
} else if strings.HasPrefix(s, "QUEUE") {
|
} else if strings.HasPrefix(s.Raw, "QUEUE") {
|
||||||
stats.Queue = s
|
stats.Queue = s.Raw
|
||||||
} else if strings.HasPrefix(s, "MEMSTATS") {
|
} else if strings.HasPrefix(s.Raw, "MEMSTATS") {
|
||||||
stats.Memstats = s
|
stats.Memstats = s.Raw
|
||||||
} else if strings.HasPrefix(s, "END") {
|
} else if strings.HasPrefix(s.Raw, "END") {
|
||||||
} else {
|
} else {
|
||||||
// return nil, errors.New(fmt.Sprintf("Unknown response, got %s.", s))
|
// return nil, errors.New(fmt.Sprintf("Unknown response, got %s.", s))
|
||||||
}
|
}
|
||||||
@@ -167,7 +178,7 @@ func (c *Clamd) Reload() error {
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case s := (<-ch):
|
case s := (<-ch):
|
||||||
switch s {
|
switch s.Raw {
|
||||||
case "RELOADING":
|
case "RELOADING":
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
@@ -191,7 +202,7 @@ func (c *Clamd) Shutdown() error {
|
|||||||
Scan file or directory (recursively) with archive support enabled (a full path is
|
Scan file or directory (recursively) with archive support enabled (a full path is
|
||||||
required).
|
required).
|
||||||
*/
|
*/
|
||||||
func (c *Clamd) ScanFile(path string) (chan string, error) {
|
func (c *Clamd) ScanFile(path string) (chan *ScanResult, error) {
|
||||||
command := fmt.Sprintf("SCAN %s", path)
|
command := fmt.Sprintf("SCAN %s", path)
|
||||||
ch, err := c.simpleCommand(command)
|
ch, err := c.simpleCommand(command)
|
||||||
return ch, err
|
return ch, err
|
||||||
@@ -201,7 +212,7 @@ func (c *Clamd) ScanFile(path string) (chan string, error) {
|
|||||||
Scan file or directory (recursively) with archive and special file support disabled
|
Scan file or directory (recursively) with archive and special file support disabled
|
||||||
(a full path is required).
|
(a full path is required).
|
||||||
*/
|
*/
|
||||||
func (c *Clamd) RawScanFile(path string) (chan string, error) {
|
func (c *Clamd) RawScanFile(path string) (chan *ScanResult, error) {
|
||||||
command := fmt.Sprintf("RAWSCAN %s", path)
|
command := fmt.Sprintf("RAWSCAN %s", path)
|
||||||
ch, err := c.simpleCommand(command)
|
ch, err := c.simpleCommand(command)
|
||||||
return ch, err
|
return ch, err
|
||||||
@@ -211,7 +222,7 @@ func (c *Clamd) RawScanFile(path string) (chan string, error) {
|
|||||||
Scan file in a standard way or scan directory (recursively) using multiple threads
|
Scan file in a standard way or scan directory (recursively) using multiple threads
|
||||||
(to make the scanning faster on SMP machines).
|
(to make the scanning faster on SMP machines).
|
||||||
*/
|
*/
|
||||||
func (c *Clamd) MultiScanFile(path string) (chan string, error) {
|
func (c *Clamd) MultiScanFile(path string) (chan *ScanResult, error) {
|
||||||
command := fmt.Sprintf("MULTISCAN %s", path)
|
command := fmt.Sprintf("MULTISCAN %s", path)
|
||||||
ch, err := c.simpleCommand(command)
|
ch, err := c.simpleCommand(command)
|
||||||
return ch, err
|
return ch, err
|
||||||
@@ -221,7 +232,7 @@ func (c *Clamd) MultiScanFile(path string) (chan string, error) {
|
|||||||
Scan file or directory (recursively) with archive support enabled and don’t stop
|
Scan file or directory (recursively) with archive support enabled and don’t stop
|
||||||
the scanning when a virus is found.
|
the scanning when a virus is found.
|
||||||
*/
|
*/
|
||||||
func (c *Clamd) ContScanFile(path string) (chan string, error) {
|
func (c *Clamd) ContScanFile(path string) (chan *ScanResult, error) {
|
||||||
command := fmt.Sprintf("CONTSCAN %s", path)
|
command := fmt.Sprintf("CONTSCAN %s", path)
|
||||||
ch, err := c.simpleCommand(command)
|
ch, err := c.simpleCommand(command)
|
||||||
return ch, err
|
return ch, err
|
||||||
@@ -231,7 +242,7 @@ func (c *Clamd) ContScanFile(path string) (chan string, error) {
|
|||||||
Scan file or directory (recursively) with archive support enabled and don’t stop
|
Scan file or directory (recursively) with archive support enabled and don’t stop
|
||||||
the scanning when a virus is found.
|
the scanning when a virus is found.
|
||||||
*/
|
*/
|
||||||
func (c *Clamd) AllMatchScanFile(path string) (chan string, error) {
|
func (c *Clamd) AllMatchScanFile(path string) (chan *ScanResult, error) {
|
||||||
command := fmt.Sprintf("ALLMATCHSCAN %s", path)
|
command := fmt.Sprintf("ALLMATCHSCAN %s", path)
|
||||||
ch, err := c.simpleCommand(command)
|
ch, err := c.simpleCommand(command)
|
||||||
return ch, err
|
return ch, err
|
||||||
@@ -247,7 +258,7 @@ the actual chunk. Streaming is terminated by sending a zero-length chunk. Note:
|
|||||||
do not exceed StreamMaxLength as defined in clamd.conf, otherwise clamd will
|
do not exceed StreamMaxLength as defined in clamd.conf, otherwise clamd will
|
||||||
reply with INSTREAM size limit exceeded and close the connection
|
reply with INSTREAM size limit exceeded and close the connection
|
||||||
*/
|
*/
|
||||||
func (c *Clamd) ScanStream(r io.Reader) (chan string, error) {
|
func (c *Clamd) ScanStream(r io.Reader) (chan *ScanResult, error) {
|
||||||
conn, err := c.newConnection()
|
conn, err := c.newConnection()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
49
conn.go
49
conn.go
@@ -30,6 +30,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -38,6 +40,10 @@ import (
|
|||||||
const CHUNK_SIZE = 1024
|
const CHUNK_SIZE = 1024
|
||||||
const TCP_TIMEOUT = time.Second * 2
|
const TCP_TIMEOUT = time.Second * 2
|
||||||
|
|
||||||
|
var resultRegex = regexp.MustCompile(
|
||||||
|
`^(?P<path>[^:]+): ((?P<desc>[^:]+)(\((?P<virhash>([^:]+)):(?P<virsize>\d+)\))? )?(?P<status>FOUND|ERROR|OK)$`,
|
||||||
|
)
|
||||||
|
|
||||||
type CLAMDConn struct {
|
type CLAMDConn struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
}
|
}
|
||||||
@@ -75,18 +81,13 @@ func (conn *CLAMDConn) sendChunk(data []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CLAMDConn) readResponse() (chan string, *sync.WaitGroup, error) {
|
func (c *CLAMDConn) readResponse() (chan *ScanResult, *sync.WaitGroup, error) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
||||||
// read data
|
|
||||||
reader := bufio.NewReader(c)
|
reader := bufio.NewReader(c)
|
||||||
|
ch := make(chan *ScanResult)
|
||||||
|
|
||||||
// reading
|
|
||||||
ch := make(chan string)
|
|
||||||
|
|
||||||
// var dataArrays []string
|
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
close(ch)
|
close(ch)
|
||||||
@@ -104,14 +105,44 @@ func (c *CLAMDConn) readResponse() (chan string, *sync.WaitGroup, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
line = strings.TrimRight(line, " \t\r\n")
|
line = strings.TrimRight(line, " \t\r\n")
|
||||||
|
ch <- parseResult(line)
|
||||||
ch <- line
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return ch, &wg, nil
|
return ch, &wg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseResult(line string) *ScanResult {
|
||||||
|
res := &ScanResult{}
|
||||||
|
res.Raw = line
|
||||||
|
|
||||||
|
matches := resultRegex.FindStringSubmatch(line)
|
||||||
|
if len(matches) == 0 {
|
||||||
|
res.Status = RES_PARSE_ERROR
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, name := range resultRegex.SubexpNames() {
|
||||||
|
switch name {
|
||||||
|
case "path":
|
||||||
|
res.Path = matches[i]
|
||||||
|
case "desc":
|
||||||
|
res.Description = matches[i]
|
||||||
|
case "virhash":
|
||||||
|
res.Hash = matches[i]
|
||||||
|
case "virsize":
|
||||||
|
i, err := strconv.Atoi(matches[i])
|
||||||
|
if err == nil {
|
||||||
|
res.Size = i
|
||||||
|
}
|
||||||
|
case "status":
|
||||||
|
res.Status = matches[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func newCLAMDTcpConn(address string) (*CLAMDConn, error) {
|
func newCLAMDTcpConn(address string) (*CLAMDConn, error) {
|
||||||
conn, err := net.DialTimeout("tcp", address, TCP_TIMEOUT)
|
conn, err := net.DialTimeout("tcp", address, TCP_TIMEOUT)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user