clamd 0.0.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](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