From ace0f266eb8e40affeb5aa5f61a542abda4b2adf Mon Sep 17 00:00:00 2001 From: "Dolf Schimmel (Freeaqingme)" Date: Sun, 3 Apr 2016 17:05:04 +0200 Subject: [PATCH] Parse Result instead of only returning a string --- clamd.go | 63 +++++++++++++++++++++++++++++++++----------------------- conn.go | 49 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 77 insertions(+), 35 deletions(-) diff --git a/clamd.go b/clamd.go index 32ceadd..92fe235 100644 --- a/clamd.go +++ b/clamd.go @@ -33,6 +33,13 @@ import ( "strings" ) +const ( + RES_OK = "OK" + RES_FOUND = "FOUND" + RES_ERROR = "ERROR" + RES_PARSE_ERROR = "PARSE ERROR" +) + type Clamd struct { address string } @@ -45,6 +52,15 @@ type Stats struct { 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*`) func (c *Clamd) newConnection() (conn *CLAMDConn, err error) { @@ -67,14 +83,12 @@ func (c *Clamd) newConnection() (conn *CLAMDConn, err error) { return } -func (c *Clamd) simpleCommand(command string) (chan string, error) { +func (c *Clamd) simpleCommand(command string) (chan *ScanResult, error) { conn, err := c.newConnection() if err != nil { return nil, err } - // defer conn.Close() - err = conn.sendCommand(command) if err != nil { return nil, err @@ -83,10 +97,7 @@ func (c *Clamd) simpleCommand(command string) (chan string, error) { ch, wg, err := conn.readResponse() go func() { - // wait for waitgroup wg.Wait() - - // close connection conn.Close() }() @@ -104,7 +115,7 @@ func (c *Clamd) Ping() error { select { case s := (<-ch): - switch s { + switch s.Raw { case "PONG": return nil default: @@ -118,7 +129,7 @@ func (c *Clamd) Ping() error { /* Print program and database versions. */ -func (c *Clamd) Version() (chan string, error) { +func (c *Clamd) Version() (chan *ScanResult, error) { dataArrays, err := c.simpleCommand("VERSION") return dataArrays, err } @@ -137,17 +148,17 @@ func (c *Clamd) Stats() (*Stats, error) { stats := &Stats{} for s := range ch { - if strings.HasPrefix(s, "POOLS") { - stats.Pools = strings.Trim(s[6:], " ") - } else if strings.HasPrefix(s, "STATE") { - stats.State = s - } else if strings.HasPrefix(s, "THREADS") { - stats.Threads = s - } else if strings.HasPrefix(s, "QUEUE") { - stats.Queue = s - } else if strings.HasPrefix(s, "MEMSTATS") { - stats.Memstats = s - } else if strings.HasPrefix(s, "END") { + if strings.HasPrefix(s.Raw, "POOLS") { + stats.Pools = strings.Trim(s.Raw[6:], " ") + } else if strings.HasPrefix(s.Raw, "STATE") { + stats.State = s.Raw + } else if strings.HasPrefix(s.Raw, "THREADS") { + stats.Threads = s.Raw + } else if strings.HasPrefix(s.Raw, "QUEUE") { + stats.Queue = s.Raw + } else if strings.HasPrefix(s.Raw, "MEMSTATS") { + stats.Memstats = s.Raw + } else if strings.HasPrefix(s.Raw, "END") { } else { // return nil, errors.New(fmt.Sprintf("Unknown response, got %s.", s)) } @@ -167,7 +178,7 @@ func (c *Clamd) Reload() error { select { case s := (<-ch): - switch s { + switch s.Raw { case "RELOADING": return nil default: @@ -191,7 +202,7 @@ func (c *Clamd) Shutdown() error { Scan file or directory (recursively) with archive support enabled (a full path is 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) ch, err := c.simpleCommand(command) 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 (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) ch, err := c.simpleCommand(command) 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 (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) ch, err := c.simpleCommand(command) 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 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) ch, err := c.simpleCommand(command) 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 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) ch, err := c.simpleCommand(command) 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 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() if err != nil { return nil, err diff --git a/conn.go b/conn.go index dee34a4..6e9f9aa 100644 --- a/conn.go +++ b/conn.go @@ -30,6 +30,8 @@ import ( "fmt" "io" "net" + "regexp" + "strconv" "strings" "sync" "time" @@ -38,6 +40,10 @@ import ( const CHUNK_SIZE = 1024 const TCP_TIMEOUT = time.Second * 2 +var resultRegex = regexp.MustCompile( + `^(?P[^:]+): ((?P[^:]+)(\((?P([^:]+)):(?P\d+)\))? )?(?PFOUND|ERROR|OK)$`, +) + type CLAMDConn struct { net.Conn } @@ -75,18 +81,13 @@ func (conn *CLAMDConn) sendChunk(data []byte) error { return err } -func (c *CLAMDConn) readResponse() (chan string, *sync.WaitGroup, error) { +func (c *CLAMDConn) readResponse() (chan *ScanResult, *sync.WaitGroup, error) { var wg sync.WaitGroup wg.Add(1) - - // read data reader := bufio.NewReader(c) + ch := make(chan *ScanResult) - // reading - ch := make(chan string) - - // var dataArrays []string go func() { defer func() { close(ch) @@ -104,14 +105,44 @@ func (c *CLAMDConn) readResponse() (chan string, *sync.WaitGroup, error) { } line = strings.TrimRight(line, " \t\r\n") - - ch <- line + ch <- parseResult(line) } }() 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) { conn, err := net.DialTimeout("tcp", address, TCP_TIMEOUT)