|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- /*
- Open Source Initiative OSI - The MIT License (MIT):Licensing
-
- The MIT License (MIT)
- Copyright (c) 2013 DutchCoders <http://github.com/dutchcoders/>
-
- Permission is hereby granted, free of charge, to any person obtaining a copy of
- this software and associated documentation files (the "Software"), to deal in
- the Software without restriction, including without limitation the rights to
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- of the Software, and to permit persons to whom the Software is furnished to do
- so, subject to the following conditions:
-
- The above copyright notice and this permission notice shall be included in all
- copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- SOFTWARE.
- */
-
- package clamd
-
- import (
- "errors"
- "fmt"
- "io"
- "net/url"
- "strings"
- )
-
- const (
- RES_OK = "OK"
- RES_FOUND = "FOUND"
- RES_ERROR = "ERROR"
- RES_PARSE_ERROR = "PARSE ERROR"
- )
-
- type Clamd struct {
- address string
- }
-
- type Stats struct {
- Pools string
- State string
- Threads string
- Memstats 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*`)
-
- func (c *Clamd) newConnection() (conn *CLAMDConn, err error) {
-
- var u *url.URL
-
- if u, err = url.Parse(c.address); err != nil {
- return
- }
-
- switch u.Scheme {
- case "tcp":
- conn, err = newCLAMDTcpConn(u.Host)
- case "unix":
- conn, err = newCLAMDUnixConn(u.Path)
- default:
- conn, err = newCLAMDUnixConn(c.address)
- }
-
- return
- }
-
- func (c *Clamd) simpleCommand(command string) (chan *ScanResult, error) {
- conn, err := c.newConnection()
- if err != nil {
- return nil, err
- }
-
- err = conn.sendCommand(command)
- if err != nil {
- return nil, err
- }
-
- ch, wg, err := conn.readResponse()
-
- go func() {
- wg.Wait()
- conn.Close()
- }()
-
- return ch, err
- }
-
- /*
- Check the daemon's state (should reply with PONG).
- */
- func (c *Clamd) Ping() error {
- ch, err := c.simpleCommand("PING")
- if err != nil {
- return err
- }
-
- select {
- case s := (<-ch):
- switch s.Raw {
- case "PONG":
- return nil
- default:
- return errors.New(fmt.Sprintf("Invalid response, got %s.", s))
- }
- }
-
- return nil
- }
-
- /*
- Print program and database versions.
- */
- func (c *Clamd) Version() (chan *ScanResult, error) {
- dataArrays, err := c.simpleCommand("VERSION")
- return dataArrays, err
- }
-
- /*
- On this command clamd provides statistics about the scan queue, contents of scan
- queue, and memory usage. The exact reply format is subject to changes in future
- releases.
- */
- func (c *Clamd) Stats() (*Stats, error) {
- ch, err := c.simpleCommand("STATS")
- if err != nil {
- return nil, err
- }
-
- stats := &Stats{}
-
- for s := range ch {
- 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))
- }
- }
-
- return stats, nil
- }
-
- /*
- Reload the databases.
- */
- func (c *Clamd) Reload() error {
- ch, err := c.simpleCommand("RELOAD")
- if err != nil {
- return err
- }
-
- select {
- case s := (<-ch):
- switch s.Raw {
- case "RELOADING":
- return nil
- default:
- return errors.New(fmt.Sprintf("Invalid response, got %s.", s))
- }
- }
-
- return nil
- }
-
- func (c *Clamd) Shutdown() error {
- _, err := c.simpleCommand("SHUTDOWN")
- if err != nil {
- return err
- }
-
- return err
- }
-
- /*
- Scan file or directory (recursively) with archive support enabled (a full path is
- required).
- */
- func (c *Clamd) ScanFile(path string) (chan *ScanResult, error) {
- command := fmt.Sprintf("SCAN %s", path)
- ch, err := c.simpleCommand(command)
- return ch, err
- }
-
- /*
- Scan file or directory (recursively) with archive and special file support disabled
- (a full path is required).
- */
- func (c *Clamd) RawScanFile(path string) (chan *ScanResult, error) {
- command := fmt.Sprintf("RAWSCAN %s", path)
- ch, err := c.simpleCommand(command)
- return ch, err
- }
-
- /*
- 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 *ScanResult, error) {
- command := fmt.Sprintf("MULTISCAN %s", path)
- ch, err := c.simpleCommand(command)
- return ch, err
- }
-
- /*
- 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 *ScanResult, error) {
- command := fmt.Sprintf("CONTSCAN %s", path)
- ch, err := c.simpleCommand(command)
- return ch, err
- }
-
- /*
- 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 *ScanResult, error) {
- command := fmt.Sprintf("ALLMATCHSCAN %s", path)
- ch, err := c.simpleCommand(command)
- return ch, err
- }
-
- /*
- Scan a stream of data. The stream is sent to clamd in chunks, after INSTREAM,
- on the same socket on which the command was sent. This avoids the overhead
- of establishing new TCP connections and problems with NAT. The format of the
- chunk is: <length><data> where <length> is the size of the following data in
- bytes expressed as a 4 byte unsigned integer in network byte order and <data> is
- 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, abort chan bool) (chan *ScanResult, error) {
- conn, err := c.newConnection()
- if err != nil {
- return nil, err
- }
-
- go func() {
- for {
- _, allowRunning := <-abort
- if !allowRunning {
- break
- }
- }
- conn.Close()
- }()
-
- conn.sendCommand("INSTREAM")
-
- for {
- buf := make([]byte, CHUNK_SIZE)
-
- nr, err := r.Read(buf)
- if nr > 0 {
- conn.sendChunk(buf[0:nr])
- }
-
- if err != nil {
- break
- }
-
- }
-
- err = conn.sendEOF()
- if err != nil {
- return nil, err
- }
-
- ch, wg, err := conn.readResponse()
-
- go func() {
- wg.Wait()
- conn.Close()
- }()
-
- return ch, nil
- }
-
- func NewClamd(address string) *Clamd {
- clamd := &Clamd{address: address}
- return clamd
- }
|