You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

312 lines
6.8KB

  1. /*
  2. Open Source Initiative OSI - The MIT License (MIT):Licensing
  3. The MIT License (MIT)
  4. Copyright (c) 2013 DutchCoders <http://github.com/dutchcoders/>
  5. Permission is hereby granted, free of charge, to any person obtaining a copy of
  6. this software and associated documentation files (the "Software"), to deal in
  7. the Software without restriction, including without limitation the rights to
  8. use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  9. of the Software, and to permit persons to whom the Software is furnished to do
  10. so, subject to the following conditions:
  11. The above copyright notice and this permission notice shall be included in all
  12. copies or substantial portions of the Software.
  13. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  19. SOFTWARE.
  20. */
  21. package clamd
  22. import (
  23. "errors"
  24. "fmt"
  25. "io"
  26. "net/url"
  27. "strings"
  28. )
  29. const (
  30. RES_OK = "OK"
  31. RES_FOUND = "FOUND"
  32. RES_ERROR = "ERROR"
  33. RES_PARSE_ERROR = "PARSE ERROR"
  34. )
  35. type Clamd struct {
  36. address string
  37. }
  38. type Stats struct {
  39. Pools string
  40. State string
  41. Threads string
  42. Memstats string
  43. Queue string
  44. }
  45. type ScanResult struct {
  46. Raw string
  47. Description string
  48. Path string
  49. Hash string
  50. Size int
  51. Status string
  52. }
  53. var EICAR = []byte(`X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*`)
  54. func (c *Clamd) newConnection() (conn *CLAMDConn, err error) {
  55. var u *url.URL
  56. if u, err = url.Parse(c.address); err != nil {
  57. return
  58. }
  59. switch u.Scheme {
  60. case "tcp":
  61. conn, err = newCLAMDTcpConn(u.Host)
  62. case "unix":
  63. conn, err = newCLAMDUnixConn(u.Path)
  64. default:
  65. conn, err = newCLAMDUnixConn(c.address)
  66. }
  67. return
  68. }
  69. func (c *Clamd) simpleCommand(command string) (chan *ScanResult, error) {
  70. conn, err := c.newConnection()
  71. if err != nil {
  72. return nil, err
  73. }
  74. err = conn.sendCommand(command)
  75. if err != nil {
  76. return nil, err
  77. }
  78. ch, wg, err := conn.readResponse()
  79. go func() {
  80. wg.Wait()
  81. conn.Close()
  82. }()
  83. return ch, err
  84. }
  85. /*
  86. Check the daemon's state (should reply with PONG).
  87. */
  88. func (c *Clamd) Ping() error {
  89. ch, err := c.simpleCommand("PING")
  90. if err != nil {
  91. return err
  92. }
  93. select {
  94. case s := (<-ch):
  95. switch s.Raw {
  96. case "PONG":
  97. return nil
  98. default:
  99. return errors.New(fmt.Sprintf("Invalid response, got %s.", s))
  100. }
  101. }
  102. return nil
  103. }
  104. /*
  105. Print program and database versions.
  106. */
  107. func (c *Clamd) Version() (chan *ScanResult, error) {
  108. dataArrays, err := c.simpleCommand("VERSION")
  109. return dataArrays, err
  110. }
  111. /*
  112. On this command clamd provides statistics about the scan queue, contents of scan
  113. queue, and memory usage. The exact reply format is subject to changes in future
  114. releases.
  115. */
  116. func (c *Clamd) Stats() (*Stats, error) {
  117. ch, err := c.simpleCommand("STATS")
  118. if err != nil {
  119. return nil, err
  120. }
  121. stats := &Stats{}
  122. for s := range ch {
  123. if strings.HasPrefix(s.Raw, "POOLS") {
  124. stats.Pools = strings.Trim(s.Raw[6:], " ")
  125. } else if strings.HasPrefix(s.Raw, "STATE") {
  126. stats.State = s.Raw
  127. } else if strings.HasPrefix(s.Raw, "THREADS") {
  128. stats.Threads = s.Raw
  129. } else if strings.HasPrefix(s.Raw, "QUEUE") {
  130. stats.Queue = s.Raw
  131. } else if strings.HasPrefix(s.Raw, "MEMSTATS") {
  132. stats.Memstats = s.Raw
  133. } else if strings.HasPrefix(s.Raw, "END") {
  134. } else {
  135. // return nil, errors.New(fmt.Sprintf("Unknown response, got %s.", s))
  136. }
  137. }
  138. return stats, nil
  139. }
  140. /*
  141. Reload the databases.
  142. */
  143. func (c *Clamd) Reload() error {
  144. ch, err := c.simpleCommand("RELOAD")
  145. if err != nil {
  146. return err
  147. }
  148. select {
  149. case s := (<-ch):
  150. switch s.Raw {
  151. case "RELOADING":
  152. return nil
  153. default:
  154. return errors.New(fmt.Sprintf("Invalid response, got %s.", s))
  155. }
  156. }
  157. return nil
  158. }
  159. func (c *Clamd) Shutdown() error {
  160. _, err := c.simpleCommand("SHUTDOWN")
  161. if err != nil {
  162. return err
  163. }
  164. return err
  165. }
  166. /*
  167. Scan file or directory (recursively) with archive support enabled (a full path is
  168. required).
  169. */
  170. func (c *Clamd) ScanFile(path string) (chan *ScanResult, error) {
  171. command := fmt.Sprintf("SCAN %s", path)
  172. ch, err := c.simpleCommand(command)
  173. return ch, err
  174. }
  175. /*
  176. Scan file or directory (recursively) with archive and special file support disabled
  177. (a full path is required).
  178. */
  179. func (c *Clamd) RawScanFile(path string) (chan *ScanResult, error) {
  180. command := fmt.Sprintf("RAWSCAN %s", path)
  181. ch, err := c.simpleCommand(command)
  182. return ch, err
  183. }
  184. /*
  185. Scan file in a standard way or scan directory (recursively) using multiple threads
  186. (to make the scanning faster on SMP machines).
  187. */
  188. func (c *Clamd) MultiScanFile(path string) (chan *ScanResult, error) {
  189. command := fmt.Sprintf("MULTISCAN %s", path)
  190. ch, err := c.simpleCommand(command)
  191. return ch, err
  192. }
  193. /*
  194. Scan file or directory (recursively) with archive support enabled and don’t stop
  195. the scanning when a virus is found.
  196. */
  197. func (c *Clamd) ContScanFile(path string) (chan *ScanResult, error) {
  198. command := fmt.Sprintf("CONTSCAN %s", path)
  199. ch, err := c.simpleCommand(command)
  200. return ch, err
  201. }
  202. /*
  203. Scan file or directory (recursively) with archive support enabled and don’t stop
  204. the scanning when a virus is found.
  205. */
  206. func (c *Clamd) AllMatchScanFile(path string) (chan *ScanResult, error) {
  207. command := fmt.Sprintf("ALLMATCHSCAN %s", path)
  208. ch, err := c.simpleCommand(command)
  209. return ch, err
  210. }
  211. /*
  212. Scan a stream of data. The stream is sent to clamd in chunks, after INSTREAM,
  213. on the same socket on which the command was sent. This avoids the overhead
  214. of establishing new TCP connections and problems with NAT. The format of the
  215. chunk is: <length><data> where <length> is the size of the following data in
  216. bytes expressed as a 4 byte unsigned integer in network byte order and <data> is
  217. the actual chunk. Streaming is terminated by sending a zero-length chunk. Note:
  218. do not exceed StreamMaxLength as defined in clamd.conf, otherwise clamd will
  219. reply with INSTREAM size limit exceeded and close the connection
  220. */
  221. func (c *Clamd) ScanStream(r io.Reader, abort chan bool) (chan *ScanResult, error) {
  222. conn, err := c.newConnection()
  223. if err != nil {
  224. return nil, err
  225. }
  226. go func() {
  227. for {
  228. _, allowRunning := <-abort
  229. if !allowRunning {
  230. break
  231. }
  232. }
  233. conn.Close()
  234. }()
  235. conn.sendCommand("INSTREAM")
  236. for {
  237. buf := make([]byte, CHUNK_SIZE)
  238. nr, err := r.Read(buf)
  239. if nr > 0 {
  240. conn.sendChunk(buf[0:nr])
  241. }
  242. if err != nil {
  243. break
  244. }
  245. }
  246. err = conn.sendEOF()
  247. if err != nil {
  248. return nil, err
  249. }
  250. ch, wg, err := conn.readResponse()
  251. go func() {
  252. wg.Wait()
  253. conn.Close()
  254. }()
  255. return ch, nil
  256. }
  257. func NewClamd(address string) *Clamd {
  258. clamd := &Clamd{address: address}
  259. return clamd
  260. }