clamd 0.0.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -0
- data/Gemfile +2 -0
- data/README.md +63 -44
- data/bin/clamdc +9 -0
- data/clamd.gemspec +8 -10
- data/lib/clamd.rb +30 -2
- data/lib/clamd/client.rb +179 -0
- data/lib/clamd/configuration.rb +16 -27
- data/lib/clamd/socket_manager.rb +87 -0
- data/lib/clamd/version.rb +1 -1
- data/spec/fixtures/clamdoc.pdf +0 -0
- data/spec/fixtures/documents/clamdoc.pdf +0 -0
- data/spec/fixtures/documents/clamdoc1.pdf +0 -0
- data/spec/fixtures/virus +1 -0
- data/spec/lib/clamd/client_spec.rb +67 -0
- data/spec/spec_helper.rb +15 -0
- metadata +24 -12
- data/LICENSE.txt +0 -22
- data/Rakefile +0 -1
- data/lib/clamd/command.rb +0 -100
- data/lib/clamd/instream_helper.rb +0 -26
- data/lib/clamd/socket_utility.rb +0 -13
data/.rspec
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# Clamd
|
2
2
|
|
3
|
-
Ruby
|
4
|
-
|
5
|
-
NOTE: Still under development
|
3
|
+
Ruby client to interact with ClamAV daemon
|
6
4
|
|
7
5
|
## Installation
|
8
6
|
|
@@ -14,76 +12,97 @@ And then execute:
|
|
14
12
|
|
15
13
|
$ bundle
|
16
14
|
|
17
|
-
|
15
|
+
Install clamd directly
|
18
16
|
|
19
17
|
$ gem install clamd
|
20
18
|
|
21
19
|
## Configuration
|
22
20
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
config.
|
29
|
-
config.
|
30
|
-
config.
|
31
|
-
|
21
|
+
Clamd by default connects to 9321 port in localhost. You can also configure the
|
22
|
+
host, port, open_timeout(seconds), read_timeout(seconds) and chunk_size(bytes).
|
23
|
+
Refer the following code to configure Clamd.
|
24
|
+
|
25
|
+
Client.configure do |config|
|
26
|
+
config.host = 'localhost'
|
27
|
+
config.port = 9321
|
28
|
+
config.open_timeout = 5
|
29
|
+
config.read_timeout = 20
|
30
|
+
config.chunk_size = 102400
|
31
|
+
end
|
32
32
|
|
33
33
|
## Usage
|
34
34
|
|
35
|
-
|
35
|
+
@clamd = Clamd::Client.new
|
36
36
|
|
37
|
-
|
37
|
+
### PING
|
38
38
|
|
39
|
-
|
39
|
+
@clamd.ping
|
40
|
+
=>"PONG"
|
40
41
|
|
41
|
-
|
42
|
+
### RELOAD
|
42
43
|
|
43
|
-
|
44
|
+
@clamd.reload
|
45
|
+
=>"RELOADING"
|
44
46
|
|
45
|
-
|
47
|
+
### SHUTDOWN
|
46
48
|
|
47
|
-
|
49
|
+
@clamd.shutdown
|
50
|
+
=> true
|
48
51
|
|
49
|
-
|
52
|
+
### SCAN
|
50
53
|
|
51
|
-
|
54
|
+
@clamd.scan("/file/path")
|
55
|
+
=>"/file/path: OK"
|
52
56
|
|
53
|
-
|
57
|
+
### CONTSCAN
|
54
58
|
|
55
|
-
|
59
|
+
@clamd.contscan("/file/path")
|
60
|
+
=>"/file/path: OK"
|
56
61
|
|
57
|
-
|
62
|
+
### MULTISCAN
|
63
|
+
|
64
|
+
@clamd.multiscan("/file/path")
|
65
|
+
=>"/file/path: OK"
|
58
66
|
|
59
|
-
|
67
|
+
### INSTREAM
|
60
68
|
|
61
|
-
|
69
|
+
@clamd.instream("/file/path/to/stream/to/clamd")
|
70
|
+
=>"stream: OK"
|
62
71
|
|
63
|
-
|
72
|
+
### STATS
|
64
73
|
|
65
|
-
|
74
|
+
@clamd.stats
|
75
|
+
=> "POOLS: 1STATE: VALID PRIMARYTHREADS: live 1 idle 0 max 12 idle-timeout 30QUEUE: 0 items"
|
66
76
|
|
67
|
-
|
77
|
+
### VERSION
|
68
78
|
|
69
|
-
|
70
|
-
=>"/
|
79
|
+
@clamd.version
|
80
|
+
=> "ClamAV 0.97.8/18237/Sat Dec 14 11:13:16 2013"
|
71
81
|
|
72
|
-
|
82
|
+
### Connecting multiple ClamdAV daemon
|
73
83
|
|
74
|
-
|
75
|
-
|
84
|
+
You can also connect to multiple ClamdAV daemon running on different machine at
|
85
|
+
the same time.
|
76
86
|
|
77
|
-
|
87
|
+
@clamd1 = Clamd::Client.new(host: '192.16.20.11', port: 9321)
|
88
|
+
@clamd2 = Clamd::Client.new(host: '172.16.50.21', port: 8321)
|
78
89
|
|
79
|
-
|
80
|
-
=>
|
90
|
+
@clamd1.ping
|
91
|
+
=> "PONG"
|
81
92
|
|
82
|
-
|
93
|
+
@clamd2.ping
|
94
|
+
=> "PONG"
|
83
95
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
96
|
+
## License
|
97
|
+
|
98
|
+
Clamd is released under the [MIT License](http://www.opensource.org/licenses/MIT).
|
99
|
+
|
100
|
+
## Code Status
|
101
|
+
|
102
|
+
[![Code Climate](https://codeclimate.com/github/soundarapandian/clamd.png)](https://codeclimate.com/github/soundarapandian/clamd)
|
103
|
+
|
104
|
+
## Test
|
105
|
+
|
106
|
+
Run spec
|
88
107
|
|
89
|
-
|
108
|
+
rspec spec/
|
data/bin/clamdc
ADDED
data/clamd.gemspec
CHANGED
@@ -1,19 +1,17 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'clamd/version'
|
2
|
+
require File.expand_path('../lib/clamd/version', __FILE__)
|
5
3
|
|
6
4
|
Gem::Specification.new do |gem|
|
7
|
-
gem.name =
|
5
|
+
gem.name = 'clamd'
|
8
6
|
gem.version = Clamd::VERSION
|
9
|
-
gem.authors = [
|
10
|
-
gem.email = [
|
7
|
+
gem.authors = ['Soundarapandian Rathinasamy']
|
8
|
+
gem.email = ['soundar.rathinasamy@gmail.com']
|
11
9
|
gem.description = %q{Ruby gem to interact with ClamAV daemon(Clamd)}
|
12
|
-
gem.summary = %q{Clamd gem enables you to feed the simple VERSION command to
|
10
|
+
gem.summary = %q{Clamd gem enables you to feed the simple VERSION command to
|
13
11
|
complex INSTREAM command to ClamAV antivirus daemon(Clamd)}
|
14
|
-
gem.homepage =
|
15
|
-
|
12
|
+
gem.homepage = 'https://github.com/soundarapandian/clamd'
|
13
|
+
gem.license = 'MIT'
|
16
14
|
gem.files = `git ls-files`.split($/)
|
17
15
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
-
gem.require_paths = [
|
16
|
+
gem.require_paths = ['lib']
|
19
17
|
end
|
data/lib/clamd.rb
CHANGED
@@ -1,7 +1,35 @@
|
|
1
|
-
require "clamd/
|
1
|
+
require "clamd/client"
|
2
2
|
require "clamd/configuration"
|
3
3
|
require "clamd/version"
|
4
4
|
|
5
5
|
module Clamd
|
6
|
-
|
6
|
+
##
|
7
|
+
# Used to configure the Clamd globally
|
8
|
+
#
|
9
|
+
# Usage:
|
10
|
+
#
|
11
|
+
# Clamd.configure do |config|
|
12
|
+
# config.host = 'localhost'
|
13
|
+
# config.port = 9321
|
14
|
+
# config.open_timeout = 5
|
15
|
+
# config.read_timeout = 10
|
16
|
+
# config.chunk_size = 10240
|
17
|
+
# end
|
18
|
+
def self.configure
|
19
|
+
@configuration ||= Configuration.new
|
20
|
+
|
21
|
+
yield(@configuration) if block_given?
|
22
|
+
|
23
|
+
@configuration
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Gets current configuration details of Clamd
|
28
|
+
|
29
|
+
def self.configuration
|
30
|
+
@configuration
|
31
|
+
end
|
32
|
+
|
33
|
+
# Configure defaults
|
34
|
+
configure
|
7
35
|
end
|
data/lib/clamd/client.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'clamd/socket_manager'
|
3
|
+
|
4
|
+
module Clamd
|
5
|
+
##
|
6
|
+
# == Class that interacts with ClamdAV daemon
|
7
|
+
#
|
8
|
+
# Clamd::Client is responsible for interacting with ClamdAV daemon.
|
9
|
+
#
|
10
|
+
# For example you can connect ClamdAV daemon as given below
|
11
|
+
#
|
12
|
+
# clamd = Clamd::Client.new
|
13
|
+
# clamd.ping
|
14
|
+
#
|
15
|
+
# In the above example the ClamdAV daemon details will be get from the
|
16
|
+
# Clamd::Configuration
|
17
|
+
#
|
18
|
+
# You can also override the globla Clamd::Configuration as given below
|
19
|
+
#
|
20
|
+
# clamd = Clamd::Client.new(host: '172.15.20.11', port: 8321)
|
21
|
+
# clamd.ping
|
22
|
+
|
23
|
+
class Client
|
24
|
+
attr_accessor :host, :port, :open_timeout, :read_timeout, :chunk_size
|
25
|
+
|
26
|
+
##
|
27
|
+
# Supported ClamdAV daemon commands
|
28
|
+
|
29
|
+
COMMAND = {
|
30
|
+
ping: 'PING',
|
31
|
+
version: 'VERSION',
|
32
|
+
reload: 'RELOAD',
|
33
|
+
shutdown: 'SHUTDOWN',
|
34
|
+
scan: 'SCAN',
|
35
|
+
contscan: 'CONTSCAN',
|
36
|
+
multiscan: 'MULTISCAN',
|
37
|
+
instream: 'zINSTREAM\0',
|
38
|
+
stats: 'zSTATS\0'
|
39
|
+
}.freeze
|
40
|
+
|
41
|
+
include SocketManager
|
42
|
+
|
43
|
+
def initialize(options = {})
|
44
|
+
self.host = options[:host] || Clamd.configuration.host
|
45
|
+
self.port = options[:port] || Clamd.configuration.port
|
46
|
+
self.open_timeout = options[:open_timeout] || Clamd.configuration.open_timeout
|
47
|
+
self.read_timeout = options[:read_timeout] || Clamd.configuration.read_timeout
|
48
|
+
self.chunk_size = options[:chunk_size] || Clamd.configuration.chunk_size
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Method used to feed PING command to ClamdAV daemon
|
53
|
+
#
|
54
|
+
# Usage:
|
55
|
+
#
|
56
|
+
# clamd = Clamd::Client.new
|
57
|
+
# clamd.ping
|
58
|
+
def ping
|
59
|
+
exec(COMMAND[:ping])
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Method used to feed VERSION command to ClamdAV daemon
|
64
|
+
#
|
65
|
+
# Usage:
|
66
|
+
#
|
67
|
+
# clamd = Clamd::Client.new
|
68
|
+
# clamd.version
|
69
|
+
|
70
|
+
def version
|
71
|
+
exec(COMMAND[:version])
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Method used to feed RELOAD command to ClamdAV daemon
|
76
|
+
#
|
77
|
+
# Usage:
|
78
|
+
#
|
79
|
+
# clamd = Clamd::Client.new
|
80
|
+
# clamd.reload
|
81
|
+
|
82
|
+
def reload
|
83
|
+
exec(COMMAND[:reload])
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# Method used to feed SHUTDOWN command to ClamdAV daemon
|
88
|
+
#
|
89
|
+
# Usage:
|
90
|
+
#
|
91
|
+
# clamd = Clamd::Client.new
|
92
|
+
# clamd.shutdown
|
93
|
+
#
|
94
|
+
# Return value: +true+ on success +false+ on failure
|
95
|
+
|
96
|
+
def shutdown
|
97
|
+
exec(COMMAND[:shutdown])
|
98
|
+
|
99
|
+
ping =~ /^ERROR: Connection refused.*$/ ? true : false
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
# Method used to feed SCAN command to ClamdAV daemon
|
104
|
+
#
|
105
|
+
# Usage:
|
106
|
+
#
|
107
|
+
# clamd = Clamd::Client.new
|
108
|
+
# clamd.scan('/home/soundar/documents/doc.pdf') (file)
|
109
|
+
# clamd.scan('/home/soundar/documents') (directory)
|
110
|
+
|
111
|
+
def scan(path)
|
112
|
+
exec(COMMAND[:scan], path)
|
113
|
+
end
|
114
|
+
|
115
|
+
##
|
116
|
+
# Method used to feed CONTSCAN command to ClamdAV daemon
|
117
|
+
#
|
118
|
+
# Usage:
|
119
|
+
#
|
120
|
+
# clamd = Clamd::Client.new
|
121
|
+
# clamd.contscan('/home/soundar/documents/doc.pdf') (file)
|
122
|
+
# clamd.contscan('/home/soundar/documents') (directory)
|
123
|
+
|
124
|
+
def contscan(path)
|
125
|
+
exec(COMMAND[:contscan], path)
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# Method used to feed MULTISCAN command to ClamdAV daemon
|
130
|
+
#
|
131
|
+
# Usage:
|
132
|
+
#
|
133
|
+
# clamd = Clamd::Client.new
|
134
|
+
# clamd.multiscan('/home/soundar/documents/doc.pdf') # file
|
135
|
+
# clamd.multiscan('/home/soundar/documents') # directory
|
136
|
+
|
137
|
+
def multiscan(path)
|
138
|
+
exec(COMMAND[:multiscan], path)
|
139
|
+
end
|
140
|
+
|
141
|
+
##
|
142
|
+
# Method used to feed INSTREAM command to ClamdAV daemon
|
143
|
+
#
|
144
|
+
# Usage:
|
145
|
+
#
|
146
|
+
# clamd = Clamd::Client.new
|
147
|
+
# clamd.instream('/home/soundar/documents/doc.pdf') # file
|
148
|
+
|
149
|
+
def instream(path)
|
150
|
+
exec(COMMAND[:instream], path)
|
151
|
+
end
|
152
|
+
|
153
|
+
##
|
154
|
+
# Method used to feed STATS command to ClamdAV daemon
|
155
|
+
#
|
156
|
+
# Usage:
|
157
|
+
#
|
158
|
+
# clamd = Clamd::Client.new
|
159
|
+
# clamd.stats
|
160
|
+
|
161
|
+
def stats
|
162
|
+
exec(COMMAND[:stats])
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
def exec(command, path=nil)
|
168
|
+
begin
|
169
|
+
socket = Timeout::timeout(open_timeout) { open_socket(host, port) }
|
170
|
+
write_socket(socket, command, path)
|
171
|
+
Timeout::timeout(read_timeout) { read_socket(socket, command) }
|
172
|
+
rescue Exception => e
|
173
|
+
"ERROR: #{e.message.gsub('ERROR', '')}"
|
174
|
+
ensure
|
175
|
+
close_socket(socket) if socket
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
data/lib/clamd/configuration.rb
CHANGED
@@ -1,35 +1,24 @@
|
|
1
1
|
module Clamd
|
2
2
|
class Configuration
|
3
3
|
attr_accessor :host, :port, :open_timeout, :read_timeout, :chunk_size
|
4
|
-
DEFAULT_CONFIGURATION = { :open_timeout => 5, :read_timeout => 30, :chunk_size => 10240 }
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
@read_timeout = DEFAULT_CONFIGURATION[:read_timeout]
|
9
|
-
@chunk_size = DEFAULT_CONFIGURATION[:chunk_size]
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.configure
|
14
|
-
@configuration ||= Configuration.new
|
15
|
-
yield(@configuration) if block_given?
|
16
|
-
missing = []
|
17
|
-
missing << "host" unless @configuration.host
|
18
|
-
missing << "port" unless @configuration.port
|
19
|
-
raise ConfigurationError,
|
20
|
-
"Missing configuration: #{missing.join(",")}" unless missing.empty?
|
21
|
-
@configuration
|
22
|
-
end
|
5
|
+
##
|
6
|
+
# Default configuration for Clamd
|
23
7
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
8
|
+
DEFAULTS = {
|
9
|
+
host: 'localhost',
|
10
|
+
port: 9321,
|
11
|
+
open_timeout: 5,
|
12
|
+
read_timeout: 30,
|
13
|
+
chunk_size: 10240
|
14
|
+
}
|
28
15
|
|
29
|
-
|
30
|
-
|
31
|
-
|
16
|
+
def initialize
|
17
|
+
self.host = DEFAULTS[:host]
|
18
|
+
self.port = DEFAULTS[:port]
|
19
|
+
self.open_timeout = DEFAULTS[:open_timeout]
|
20
|
+
self.read_timeout = DEFAULTS[:read_timeout]
|
21
|
+
self.chunk_size = DEFAULTS[:chunk_size]
|
22
|
+
end
|
32
23
|
end
|
33
|
-
|
34
|
-
class ConfigurationError < StandardError;end
|
35
24
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Clamd
|
4
|
+
module SocketManager
|
5
|
+
##
|
6
|
+
# Opens socket for the given +host+ and +port+
|
7
|
+
|
8
|
+
def open_socket(host, port)
|
9
|
+
TCPSocket.open(host, port)
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# Closes the given socket
|
14
|
+
|
15
|
+
def close_socket(socket)
|
16
|
+
socket.close
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Reads the response for the given command from the socket
|
21
|
+
|
22
|
+
def read_socket(socket, command)
|
23
|
+
socket.recv(clamd_response_size(command)).gsub(/(\u0000)|(\n)/, "").strip
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Writes the command to the given sicket
|
28
|
+
|
29
|
+
def write_socket(socket, command, path)
|
30
|
+
if path && command != 'zINSTREAM\0'
|
31
|
+
socket.write("#{command} #{path}")
|
32
|
+
else
|
33
|
+
socket.write(command)
|
34
|
+
end
|
35
|
+
stream_to_clamd(socket, path) if command == 'zINSTREAM\0'
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Determines the number of bytes to be read for the command
|
40
|
+
|
41
|
+
def clamd_response_size(command)
|
42
|
+
case command
|
43
|
+
when 'PING'
|
44
|
+
4
|
45
|
+
when 'RELOAD'
|
46
|
+
9
|
47
|
+
when 'SHUTDOWN'
|
48
|
+
1
|
49
|
+
else
|
50
|
+
1024
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Streams file content to the ClamAV daemon
|
56
|
+
|
57
|
+
def stream_to_clamd(socket, path)
|
58
|
+
begin
|
59
|
+
file = File.open(path, "rb")
|
60
|
+
bytes = file.read(chunk_size)
|
61
|
+
|
62
|
+
while bytes
|
63
|
+
write_chunk(socket, bytes)
|
64
|
+
bytes = file.read(chunk_size)
|
65
|
+
end
|
66
|
+
stop_streaming(socket)
|
67
|
+
ensure
|
68
|
+
file.close if file
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Writes the size of chunk(bytes), chunk(bytes) to the socket
|
74
|
+
|
75
|
+
def write_chunk(socket, chunk)
|
76
|
+
socket.write([chunk.size].pack("N"))
|
77
|
+
socket.write(chunk)
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Stops streaming to ClamAV daemon
|
82
|
+
|
83
|
+
def stop_streaming(socket)
|
84
|
+
socket.write([0].pack("N"))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/clamd/version.rb
CHANGED
Binary file
|
Binary file
|
Binary file
|
data/spec/fixtures/virus
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Clamd::Client do
|
4
|
+
let(:client) {described_class.new}
|
5
|
+
let(:file_path) {File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'clamdoc.pdf'))}
|
6
|
+
let(:directory_path) {File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'documents'))}
|
7
|
+
let(:file_with_virus_path) {File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'virus'))}
|
8
|
+
|
9
|
+
shared_examples 'virus scanner' do |mode|
|
10
|
+
it 'scans the given file' do
|
11
|
+
expect(client.send(mode, file_path)).to match(/^.*: OK$/)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'scans the given directory' do
|
15
|
+
expect(client.send(mode, directory_path)).to match(/^.*: OK$/)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "reports virus if found" do
|
19
|
+
expect(client.send(mode, file_with_virus_path)).to match(/^.*: (.+?) FOUND$/)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#ping' do
|
24
|
+
it 'gets PONG if ClamAV daemon alive' do
|
25
|
+
expect(client.ping).to eq('PONG')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#version' do
|
30
|
+
it 'gets version of the ClamAV' do
|
31
|
+
expect(client.version).to match(/^ClamAV \d+.\d+.\d+/)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#reload' do
|
36
|
+
it 'reloads ClamAV virus signature database' do
|
37
|
+
expect(client.reload).to eq('RELOADING')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#scan' do
|
42
|
+
include_examples 'virus scanner', 'scan'
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#multiscan' do
|
46
|
+
include_examples 'virus scanner', 'multiscan'
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#contscan' do
|
50
|
+
include_examples 'virus scanner', 'contscan'
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'supports to connect multiple ClamAV daemon with different configuration' do
|
54
|
+
clamd1 = described_class.new(host: 'localhost')
|
55
|
+
clamd2 = described_class.new(host: '127.0.0.1')
|
56
|
+
|
57
|
+
expect(clamd1.ping).to eq('PONG')
|
58
|
+
expect(clamd2.ping).to eq('PONG')
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'reads global configuration if not specified for current client' do
|
62
|
+
clamd = described_class.new
|
63
|
+
|
64
|
+
expect(clamd.host).to eq('localhost')
|
65
|
+
expect(clamd.port).to eq(9321)
|
66
|
+
end
|
67
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'clamd'
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
7
|
+
config.run_all_when_everything_filtered = true
|
8
|
+
config.filter_run :focus
|
9
|
+
|
10
|
+
# Run specs in random order to surface order dependencies. If you find an
|
11
|
+
# order dependency and want to debug it, you can fix the order by providing
|
12
|
+
# the seed, which is printed after each run.
|
13
|
+
# --seed 1234
|
14
|
+
config.order = 'random'
|
15
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: clamd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
|
-
-
|
8
|
+
- Soundarapandian Rathinasamy
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-12-19 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Ruby gem to interact with ClamAV daemon(Clamd)
|
15
15
|
email:
|
@@ -19,19 +19,25 @@ extensions: []
|
|
19
19
|
extra_rdoc_files: []
|
20
20
|
files:
|
21
21
|
- .gitignore
|
22
|
+
- .rspec
|
22
23
|
- Gemfile
|
23
|
-
- LICENSE.txt
|
24
24
|
- README.md
|
25
|
-
-
|
25
|
+
- bin/clamdc
|
26
26
|
- clamd.gemspec
|
27
27
|
- lib/clamd.rb
|
28
|
-
- lib/clamd/
|
28
|
+
- lib/clamd/client.rb
|
29
29
|
- lib/clamd/configuration.rb
|
30
|
-
- lib/clamd/
|
31
|
-
- lib/clamd/socket_utility.rb
|
30
|
+
- lib/clamd/socket_manager.rb
|
32
31
|
- lib/clamd/version.rb
|
32
|
+
- spec/fixtures/clamdoc.pdf
|
33
|
+
- spec/fixtures/documents/clamdoc.pdf
|
34
|
+
- spec/fixtures/documents/clamdoc1.pdf
|
35
|
+
- spec/fixtures/virus
|
36
|
+
- spec/lib/clamd/client_spec.rb
|
37
|
+
- spec/spec_helper.rb
|
33
38
|
homepage: https://github.com/soundarapandian/clamd
|
34
|
-
licenses:
|
39
|
+
licenses:
|
40
|
+
- MIT
|
35
41
|
post_install_message:
|
36
42
|
rdoc_options: []
|
37
43
|
require_paths:
|
@@ -50,9 +56,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
56
|
version: '0'
|
51
57
|
requirements: []
|
52
58
|
rubyforge_project:
|
53
|
-
rubygems_version: 1.8.
|
59
|
+
rubygems_version: 1.8.25
|
54
60
|
signing_key:
|
55
61
|
specification_version: 3
|
56
|
-
summary: Clamd gem enables you to feed the simple VERSION command to
|
62
|
+
summary: Clamd gem enables you to feed the simple VERSION command to complex INSTREAM
|
57
63
|
command to ClamAV antivirus daemon(Clamd)
|
58
|
-
test_files:
|
64
|
+
test_files:
|
65
|
+
- spec/fixtures/clamdoc.pdf
|
66
|
+
- spec/fixtures/documents/clamdoc.pdf
|
67
|
+
- spec/fixtures/documents/clamdoc1.pdf
|
68
|
+
- spec/fixtures/virus
|
69
|
+
- spec/lib/clamd/client_spec.rb
|
70
|
+
- spec/spec_helper.rb
|
data/LICENSE.txt
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
Copyright (c) 2012 soundarapandian rathinasamy
|
2
|
-
|
3
|
-
MIT License
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
-
a copy of this software and associated documentation files (the
|
7
|
-
"Software"), to deal in the Software without restriction, including
|
8
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
-
permit persons to whom the Software is furnished to do so, subject to
|
11
|
-
the following conditions:
|
12
|
-
|
13
|
-
The above copyright notice and this permission notice shall be
|
14
|
-
included in all copies or substantial portions of the Software.
|
15
|
-
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require "bundler/gem_tasks"
|
data/lib/clamd/command.rb
DELETED
@@ -1,100 +0,0 @@
|
|
1
|
-
require 'timeout'
|
2
|
-
require 'clamd/socket_utility'
|
3
|
-
require 'clamd/instream_helper'
|
4
|
-
|
5
|
-
module Clamd
|
6
|
-
module Command
|
7
|
-
COMMAND = {
|
8
|
-
:ping => "PING",
|
9
|
-
:version => "VERSION",
|
10
|
-
:reload => "RELOAD",
|
11
|
-
:shutdown => "SHUTDOWN",
|
12
|
-
:scan => "SCAN",
|
13
|
-
:contscan => "CONTSCAN",
|
14
|
-
:multiscan => "MULTISCAN",
|
15
|
-
:instream => "zINSTREAM\0",
|
16
|
-
:stats => "zSTATS\0" }.freeze
|
17
|
-
|
18
|
-
include SocketUtility
|
19
|
-
include InstreamHelper
|
20
|
-
|
21
|
-
def exec(command, path=nil)
|
22
|
-
begin
|
23
|
-
return "ERROR: Please configure Clamd first" unless Clamd.configured?
|
24
|
-
socket = Timeout::timeout(Clamd.configuration.open_timeout) { open_socket }
|
25
|
-
write_socket(socket, command, path)
|
26
|
-
Timeout::timeout(Clamd.configuration.read_timeout) { read_socket(socket, command) }
|
27
|
-
rescue Errno::ECONNREFUSED
|
28
|
-
"ERROR: Failed to connect to Clamd daemon"
|
29
|
-
rescue Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE
|
30
|
-
"ERROR: Connection with Clamd daemon closed unexpectedly"
|
31
|
-
rescue Timeout::Error
|
32
|
-
"ERROR: Timeout error occurred"
|
33
|
-
ensure
|
34
|
-
close_socket(socket) if socket
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def read_socket(socket, command)
|
39
|
-
socket.recv(clamd_response_size(command)).gsub(/(\u0000)|(\n)/, "")
|
40
|
-
end
|
41
|
-
|
42
|
-
def write_socket(socket, command, path)
|
43
|
-
if path && command != COMMAND[:instream]
|
44
|
-
socket.write("#{command} #{path}")
|
45
|
-
else
|
46
|
-
socket.write(command)
|
47
|
-
end
|
48
|
-
stream_to_clamd(socket, path) if command == COMMAND[:instream]
|
49
|
-
end
|
50
|
-
|
51
|
-
def clamd_response_size(command)
|
52
|
-
case command
|
53
|
-
when COMMAND[:ping]
|
54
|
-
4
|
55
|
-
when COMMAND[:reload]
|
56
|
-
9
|
57
|
-
when COMMAND[:shutdown]
|
58
|
-
1
|
59
|
-
else
|
60
|
-
1024
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def ping
|
65
|
-
exec(COMMAND[:ping])
|
66
|
-
end
|
67
|
-
|
68
|
-
def version
|
69
|
-
exec(COMMAND[:version])
|
70
|
-
end
|
71
|
-
|
72
|
-
def reload
|
73
|
-
exec(COMMAND[:reload])
|
74
|
-
end
|
75
|
-
|
76
|
-
def shutdown
|
77
|
-
exec(COMMAND[:shutdown])
|
78
|
-
end
|
79
|
-
|
80
|
-
def scan(path)
|
81
|
-
exec(COMMAND[:scan], path)
|
82
|
-
end
|
83
|
-
|
84
|
-
def contscan(path)
|
85
|
-
exec(COMMAND[:contscan], path)
|
86
|
-
end
|
87
|
-
|
88
|
-
def multiscan(path)
|
89
|
-
exec(COMMAND[:multiscan], path)
|
90
|
-
end
|
91
|
-
|
92
|
-
def instream(path)
|
93
|
-
exec(COMMAND[:instream], path)
|
94
|
-
end
|
95
|
-
|
96
|
-
def stats
|
97
|
-
exec(COMMAND[:stats])
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
module Clamd
|
2
|
-
module InstreamHelper
|
3
|
-
def stream_to_clamd(socket, path)
|
4
|
-
begin
|
5
|
-
file = File.open(path, "rb")
|
6
|
-
read = file.read(Clamd.configuration.chunk_size)
|
7
|
-
while read
|
8
|
-
write_chunk(socket, read)
|
9
|
-
read = file.read(10240)
|
10
|
-
end
|
11
|
-
stop_streaming(socket)
|
12
|
-
ensure
|
13
|
-
file.close if file
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def write_chunk(socket, chunk)
|
18
|
-
socket.write([chunk.size].pack("N"))
|
19
|
-
socket.write(chunk)
|
20
|
-
end
|
21
|
-
|
22
|
-
def stop_streaming(socket)
|
23
|
-
socket.write([0].pack("N"))
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
data/lib/clamd/socket_utility.rb
DELETED