Parse Result instead of only returning a string

This commit is contained in:
Dolf Schimmel (Freeaqingme)
2016-04-03 17:05:04 +02:00
parent 1b4a274e85
commit ace0f266eb
2 changed files with 77 additions and 35 deletions

View File

@@ -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 dont stop Scan file or directory (recursively) with archive support enabled and dont 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 dont stop Scan file or directory (recursively) with archive support enabled and dont 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
View File

@@ -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)