op-clamav-client 3.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/ChangeLog.md +64 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +674 -0
- data/README.md +209 -0
- data/Rakefile +104 -0
- data/clamav-client.gemspec +25 -0
- data/lib/clamav/client.rb +182 -0
- data/lib/clamav/commands/command.rb +38 -0
- data/lib/clamav/commands/instream_command.rb +39 -0
- data/lib/clamav/commands/ping_command.rb +25 -0
- data/lib/clamav/commands/quit_command.rb +25 -0
- data/lib/clamav/commands/scan_command.rb +35 -0
- data/lib/clamav/connection.rb +129 -0
- data/lib/clamav/response.rb +28 -0
- data/lib/clamav/responses/error_response.rb +25 -0
- data/lib/clamav/responses/success_response.rb +25 -0
- data/lib/clamav/responses/virus_response.rb +26 -0
- data/lib/clamav/util.rb +32 -0
- data/lib/clamav/wrapper.rb +33 -0
- data/lib/clamav/wrappers/new_line_wrapper.rb +29 -0
- data/lib/clamav/wrappers/null_termination_wrapper.rb +29 -0
- data/lib/clamav.rb +1 -0
- data/test/Dockerfile +16 -0
- data/test/integration/clamav/client_test.rb +142 -0
- data/test/integration/clamav/util_test.rb +41 -0
- data/test/test_helper.rb +21 -0
- data/test/unit/clamav/client_test.rb +59 -0
- data/test/unit/clamav/commands/instream_command_test.rb +53 -0
- data/test/unit/clamav/commands/ping_command_test.rb +33 -0
- data/test/unit/clamav/commands/scan_command_test.rb +36 -0
- data/test/unit/clamav/connection_test.rb +46 -0
- data/test/unit/clamav/wrappers/new_line_wrapper_test.rb +38 -0
- data/test/unit/clamav/wrappers/null_termination_wrapper_test.rb +38 -0
- metadata +143 -0
data/README.md
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
# ClamAV::Client
|
2
|
+
|
3
|
+
ClamAV::Client is a client library that can talk to the clam daemon.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'op-clamav-client', require: 'clamav/client'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install op-client-client
|
18
|
+
|
19
|
+
Alternatively, you can spawn a `pry` console right away by just running:
|
20
|
+
|
21
|
+
$ rake console
|
22
|
+
|
23
|
+
### Installing ClamAV's daemon
|
24
|
+
|
25
|
+
#### On OSX
|
26
|
+
|
27
|
+
If you are using brew, just run
|
28
|
+
|
29
|
+
```shell
|
30
|
+
brew install clamav
|
31
|
+
```
|
32
|
+
#### On Linux (Ubuntu)
|
33
|
+
```shell
|
34
|
+
sudo apt-get install clamav-daemon clamav-freshclam clamav-unofficial-sigs
|
35
|
+
sudo freshclam
|
36
|
+
sudo service clamav-daemon start
|
37
|
+
```
|
38
|
+
#### On Linux (RedHat, CentOS)
|
39
|
+
```shell
|
40
|
+
sudo yum install clamd clamav clamav-db
|
41
|
+
sudo freshclam
|
42
|
+
sudo service clamd start
|
43
|
+
```
|
44
|
+
Under RedHat/CentOS the UNIX Socket, located at `/var/run/clamav/clamd.sock`
|
45
|
+
|
46
|
+
## Requirements
|
47
|
+
|
48
|
+
* Ruby >= 2.4
|
49
|
+
* clamd
|
50
|
+
|
51
|
+
## Usage
|
52
|
+
|
53
|
+
The `ClamAV::Client` class is responsible for opening a connection to a remote
|
54
|
+
ClamAV clam daemon.
|
55
|
+
|
56
|
+
See the implemented commands below.
|
57
|
+
|
58
|
+
### PING => Boolean
|
59
|
+
|
60
|
+
Pings the daemon to check whether it is alive.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
client = ClamAV::Client.new
|
64
|
+
client.execute(ClamAV::Commands::PingCommand.new)
|
65
|
+
# => true
|
66
|
+
```
|
67
|
+
|
68
|
+
### SCAN <file_or_directory> => Array[Response]
|
69
|
+
|
70
|
+
Scans a file or a directory for the existence of a virus.
|
71
|
+
|
72
|
+
The absolute path must be given to that command.
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
client = ClamAV::Client.new
|
76
|
+
|
77
|
+
client.execute(ClamAV::Commands::ScanCommand.new('/tmp/path/foo.c'))
|
78
|
+
# => [#<ClamAV::SuccessResponse:0x007fbf314b9478 @file="/tmp/foo.c">]
|
79
|
+
|
80
|
+
client.execute(ClamAV::Commands::ScanCommand.new('/tmp/path'))
|
81
|
+
# => [#<ClamAV::SuccessResponse:0x007fc30c273298 @file="/tmp/path/foo.c">,
|
82
|
+
# #<ClamAV::SuccessResponse:0x007fc30c272910 @file="/tmp/path/foo.cpp">]
|
83
|
+
```
|
84
|
+
|
85
|
+
### INSTREAM => Response
|
86
|
+
|
87
|
+
Scans an IO-like object for the existence of a virus.
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
client = ClamAV::Client.new
|
91
|
+
|
92
|
+
io = StringIO.new('some data')
|
93
|
+
client.execute(ClamAV::Commands::InstreamCommand.new(io))
|
94
|
+
# => [#<ClamAV::SuccessResponse:0x007fe471cabe50 @file="stream">]
|
95
|
+
```
|
96
|
+
|
97
|
+
### Custom commands
|
98
|
+
|
99
|
+
Custom commands can be given to the client. The contract between the client
|
100
|
+
and the command is through the `Command#call` method. The `call` method will
|
101
|
+
receive a `Connection` object.
|
102
|
+
|
103
|
+
Here's a simple example implementing the `VERSION` command:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
# Build the client
|
107
|
+
client = ClamAV::Client.new
|
108
|
+
|
109
|
+
# Create the command lambda
|
110
|
+
version_command = lambda { |conn| conn.send_request("VERSION") }
|
111
|
+
# => #<Proc:0x007fc0d0c14b28>
|
112
|
+
|
113
|
+
# Execute the command
|
114
|
+
client.execute(version_command)
|
115
|
+
# => "1: ClamAV 0.98.1/18489/Tue Feb 18 16:00:05 2014"
|
116
|
+
```
|
117
|
+
|
118
|
+
## Defaults
|
119
|
+
|
120
|
+
The default values in use are:
|
121
|
+
|
122
|
+
* clamd socket: UNIX Socket, located at `/var/run/clamav/clamd.ctl`;
|
123
|
+
* New-line terminated commands.
|
124
|
+
|
125
|
+
These defaults can be changed:
|
126
|
+
|
127
|
+
* by creating the object graph manually;
|
128
|
+
* by setting environment variables.
|
129
|
+
|
130
|
+
### The object graph
|
131
|
+
|
132
|
+
#### Client
|
133
|
+
|
134
|
+
The main object is the `Client` object. It is responsible for executing the commands.
|
135
|
+
It can receive a custom connection object.
|
136
|
+
|
137
|
+
#### Connection
|
138
|
+
|
139
|
+
The `Connection` object is the bridge between the raw socket object and the
|
140
|
+
protocol used between the client and the daemon.
|
141
|
+
|
142
|
+
`clamd` supports two kinds of delimiters:
|
143
|
+
|
144
|
+
* NULL terminated commands
|
145
|
+
* New-line terminated commands
|
146
|
+
|
147
|
+
The management of those delimiters is done with the wrapper argument.
|
148
|
+
|
149
|
+
#### Wrapper
|
150
|
+
|
151
|
+
The wrapper is responsible for taking the incoming request with the
|
152
|
+
`wrap_request` method, and parsing the response with the `read_response`
|
153
|
+
method.
|
154
|
+
|
155
|
+
### Environment variables
|
156
|
+
|
157
|
+
The variables can be set programmatically in your Ruby programs like
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
# from a ruby script
|
161
|
+
ENV['CLAMD_UNIX_SOCKET'] = '/some/path'
|
162
|
+
|
163
|
+
# Now the defaults are changed for any new ClamAV::Client instantiation
|
164
|
+
```
|
165
|
+
|
166
|
+
or by setting the variables before starting the Ruby process:
|
167
|
+
|
168
|
+
```
|
169
|
+
# from the command-line
|
170
|
+
export CLAMD_UNIX_SOCKET = '/some/path'
|
171
|
+
ruby my_program.rb
|
172
|
+
# or
|
173
|
+
CLAMD_UNIX_SOCKET = '/some/path' ruby my_program.rb
|
174
|
+
```
|
175
|
+
|
176
|
+
Please note that setting the `CLAMD_TCP_*` variables will have the precedence
|
177
|
+
over the `CLAMD_UNIX_SOCKET`.
|
178
|
+
|
179
|
+
#### CLAMD_UNIX_SOCKET
|
180
|
+
|
181
|
+
Sets the socket path of the ClamAV daemon.
|
182
|
+
|
183
|
+
Under RedHat/CentOS this can either be set via an environment variable (above) or in the ClamAV::Connection call
|
184
|
+
```
|
185
|
+
connection = ClamAV::Connection.new(socket: ::UNIXSocket.new('/var/run/clamav/clamd.sock'), wrapper: ::ClamAV::Wrappers::NewLineWrapper.new)
|
186
|
+
client = ClamAV::Client.new(connection)
|
187
|
+
```
|
188
|
+
|
189
|
+
#### CLAMD_TCP_HOST and CLAMD_TCP_PORT
|
190
|
+
|
191
|
+
Sets the host and port of the ClamAV daemon.
|
192
|
+
|
193
|
+
## Licensing
|
194
|
+
|
195
|
+
GPLv3. COMMERCIAL license available upon request.
|
196
|
+
|
197
|
+
## Contributing
|
198
|
+
|
199
|
+
1. Fork it ( https://github.com/opf/clamav-client/fork )
|
200
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
201
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
202
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
203
|
+
5. Create new Pull Request
|
204
|
+
|
205
|
+
### Testing
|
206
|
+
|
207
|
+
The test suite can be run locally before pushing changes :
|
208
|
+
|
209
|
+
$ docker run --rm -it $(docker build -f test/Dockerfile --build-arg RUBY_VERSION=3.2-slim -q .) rake
|
data/Rakefile
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# clamav-client - ClamAV client
|
2
|
+
# Copyright (C) 2014 Franck Verrot <franck@verrot.fr>
|
3
|
+
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
require 'rubygems'
|
18
|
+
require 'rubygems/specification'
|
19
|
+
|
20
|
+
require 'bundler'
|
21
|
+
Bundler::GemHelper.install_tasks
|
22
|
+
|
23
|
+
$:<< 'lib'
|
24
|
+
require 'clamav/client'
|
25
|
+
|
26
|
+
$stdout.puts """
|
27
|
+
ClamAV::Client Copyright (C) 2014 Franck Verrot <franck@verrot.fr>
|
28
|
+
This program comes with ABSOLUTELY NO WARRANTY; for details type `rake license'.
|
29
|
+
This is free software, and you are welcome to redistribute it
|
30
|
+
under certain conditions; type `rake license' for details.
|
31
|
+
|
32
|
+
"""
|
33
|
+
|
34
|
+
require 'rake/testtask'
|
35
|
+
Rake::TestTask.new do |t|
|
36
|
+
t.libs << "test"
|
37
|
+
t.pattern = "test/**/*_test.rb"
|
38
|
+
#t.verbose = true
|
39
|
+
#t.warning = true
|
40
|
+
end
|
41
|
+
|
42
|
+
Rake::TestTask.new(:unit_tests) do |t|
|
43
|
+
t.libs << "test"
|
44
|
+
t.pattern = "test/unit/**/*_test.rb"
|
45
|
+
#t.verbose = true
|
46
|
+
#t.warning = true
|
47
|
+
end
|
48
|
+
|
49
|
+
Rake::TestTask.new(:integration_tests) do |t|
|
50
|
+
t.libs << "test"
|
51
|
+
t.pattern = "test/integration/**/*_test.rb"
|
52
|
+
#t.verbose = true
|
53
|
+
#t.warning = true
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def gemspec
|
58
|
+
@gemspec ||= begin
|
59
|
+
file = File.expand_path('../clamav-client.gemspec', __FILE__)
|
60
|
+
eval(File.read(file), binding, file)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
desc "Clean the current directory"
|
65
|
+
task :clean do
|
66
|
+
rm_rf 'tmp'
|
67
|
+
rm_rf 'pkg'
|
68
|
+
end
|
69
|
+
|
70
|
+
desc "Run the full spec suite"
|
71
|
+
task :full => ["clean", "test"]
|
72
|
+
|
73
|
+
desc "install the gem locally"
|
74
|
+
task :install => :package do
|
75
|
+
sh %{gem install pkg/#{gemspec.name}-#{gemspec.version}}
|
76
|
+
end
|
77
|
+
|
78
|
+
desc "validate the gemspec"
|
79
|
+
task :gemspec do
|
80
|
+
gemspec.validate
|
81
|
+
end
|
82
|
+
|
83
|
+
desc "Build the gem"
|
84
|
+
task :gem => [:gemspec, :build] do
|
85
|
+
mkdir_p "pkg"
|
86
|
+
sh "gem build clamav-client.gemspec"
|
87
|
+
mv "#{gemspec.full_name}.gem", "pkg"
|
88
|
+
end
|
89
|
+
|
90
|
+
desc "Install ClamAV::Client"
|
91
|
+
task :install => :gem do
|
92
|
+
sh "gem install pkg/#{gemspec.full_name}.gem"
|
93
|
+
end
|
94
|
+
|
95
|
+
task :default => :full
|
96
|
+
|
97
|
+
task :license do
|
98
|
+
`open http://www.gnu.org/licenses/gpl.txt`
|
99
|
+
end
|
100
|
+
|
101
|
+
task :console do
|
102
|
+
require 'pry'
|
103
|
+
Pry.start
|
104
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
$:<< 'lib'
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = "op-clamav-client"
|
6
|
+
spec.version = "3.4.2"
|
7
|
+
spec.authors = ["Franck Verrot"]
|
8
|
+
spec.email = ["franck@verrot.fr"]
|
9
|
+
spec.summary = %q{ClamAV::Client connects to a Clam Anti-Virus clam daemon and send commands.}
|
10
|
+
spec.description = spec.summary
|
11
|
+
spec.homepage = "https://github.com/franckverrot/clamav-client"
|
12
|
+
spec.license = "GPL-v3"
|
13
|
+
spec.required_ruby_version = '>= 2.5'
|
14
|
+
|
15
|
+
spec.files = Dir['{lib,test}/**/*', 'LICENSE.txt', 'ChangeLog.md', 'Rakefile', 'README.md', 'Gemfile', 'clamav-client.gemspec'].reject { |f| f['test/fixtures'] }
|
16
|
+
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "pry"
|
24
|
+
spec.add_development_dependency "minitest"
|
25
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
# clamav-client - ClamAV client
|
2
|
+
# Copyright (C) 2014 Franck Verrot <franck@verrot.fr>
|
3
|
+
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
require 'socket'
|
18
|
+
require 'timeout'
|
19
|
+
|
20
|
+
module ClamAV
|
21
|
+
class Client
|
22
|
+
class Error < StandardError; end
|
23
|
+
class ConnectionError < Error; end
|
24
|
+
class ConnectTimeoutError < ConnectionError; end
|
25
|
+
class ReadTimeoutError < ConnectionError; end
|
26
|
+
class WriteTimeoutError < ConnectionError; end
|
27
|
+
|
28
|
+
attr_accessor :options
|
29
|
+
|
30
|
+
def initialize(*args)
|
31
|
+
@options = env_options
|
32
|
+
|
33
|
+
args.each do |arg|
|
34
|
+
case arg
|
35
|
+
when Connection
|
36
|
+
@connection = arg
|
37
|
+
when Hash
|
38
|
+
@options = env_options.inject({}) do |acc, (option, default)|
|
39
|
+
acc[option] = arg[option] || default
|
40
|
+
acc
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
[
|
47
|
+
:unix_socket,
|
48
|
+
:tcp_host,
|
49
|
+
:tcp_port,
|
50
|
+
:connect_timeout,
|
51
|
+
:write_timeout,
|
52
|
+
:read_timeout,
|
53
|
+
].each do |m|
|
54
|
+
define_method(m) do
|
55
|
+
options[m]
|
56
|
+
end
|
57
|
+
|
58
|
+
define_method("#{m}=") do |value|
|
59
|
+
options[m] = value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def env_options
|
64
|
+
@env_options ||= {
|
65
|
+
unix_socket: ENV.fetch('CLAMD_UNIX_SOCKET', '/var/run/clamav/clamd.ctl'),
|
66
|
+
tcp_host: ENV.fetch('CLAMD_TCP_HOST', nil),
|
67
|
+
tcp_port: ENV.fetch('CLAMD_TCP_PORT', nil),
|
68
|
+
connect_timeout: ENV.fetch("CLAMD_TCP_CONNECT_TIMEOUT", nil),
|
69
|
+
write_timeout: ENV.fetch("CLAMD_TCP_WRITE_TIMEOUT", nil),
|
70
|
+
read_timeout: ENV.fetch("CLAMD_TCP_READ_TIMEOUT", nil),
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
def execute(command)
|
75
|
+
begin
|
76
|
+
command.call(connection)
|
77
|
+
rescue Errno::ETIMEDOUT => e
|
78
|
+
disconnect!
|
79
|
+
|
80
|
+
raise ConnectTimeoutError.new(e.to_s)
|
81
|
+
rescue SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::ENETDOWN => e
|
82
|
+
disconnect!
|
83
|
+
|
84
|
+
raise ConnectionError.new(e.to_s)
|
85
|
+
rescue => e
|
86
|
+
disconnect!
|
87
|
+
|
88
|
+
raise e
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def connection
|
93
|
+
@connection ||= default_connection.tap do |conn|
|
94
|
+
connect!(conn)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def default_connection
|
99
|
+
ClamAV::Connection.new(
|
100
|
+
client: self,
|
101
|
+
socket: build_socket,
|
102
|
+
wrapper: ::ClamAV::Wrappers::NewLineWrapper.new
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
def connect!(conn=nil)
|
107
|
+
(conn || @connection).establish_connection
|
108
|
+
rescue Errno::ETIMEDOUT => e
|
109
|
+
@connection = nil
|
110
|
+
|
111
|
+
raise ConnectTimeoutError.new(e.to_s)
|
112
|
+
rescue SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED => e
|
113
|
+
@connection = nil
|
114
|
+
|
115
|
+
raise ConnectionError.new(e.to_s)
|
116
|
+
end
|
117
|
+
|
118
|
+
def disconnect!
|
119
|
+
return true if @connection.nil?
|
120
|
+
|
121
|
+
@connection.disconnect!.tap do
|
122
|
+
@connection = nil
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def tcp?
|
127
|
+
!!tcp_host && !!tcp_port
|
128
|
+
end
|
129
|
+
|
130
|
+
def file?
|
131
|
+
!tcp
|
132
|
+
end
|
133
|
+
|
134
|
+
def build_socket
|
135
|
+
return Socket.tcp(tcp_host, tcp_port, connect_timeout: connect_timeout) if tcp?
|
136
|
+
|
137
|
+
::UNIXSocket.new(unix_socket)
|
138
|
+
rescue Errno::ETIMEDOUT => e
|
139
|
+
raise ConnectTimeoutError.new(e.to_s)
|
140
|
+
rescue SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED => e
|
141
|
+
raise ConnectionError.new(e.to_s)
|
142
|
+
end
|
143
|
+
|
144
|
+
def ping
|
145
|
+
execute Commands::PingCommand.new
|
146
|
+
end
|
147
|
+
|
148
|
+
def safe?(target)
|
149
|
+
return instream(target).virus_name.nil? if target.is_a?(StringIO)
|
150
|
+
scan(target).all? { |file| file.virus_name.nil? }
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def instream(io)
|
156
|
+
execute Commands::InstreamCommand.new(io)
|
157
|
+
end
|
158
|
+
|
159
|
+
def scan(file_path)
|
160
|
+
execute Commands::ScanCommand.new(file_path)
|
161
|
+
end
|
162
|
+
|
163
|
+
def quit
|
164
|
+
execute Commands::QuitCommand.new
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
require_relative "connection"
|
170
|
+
require_relative "commands/command"
|
171
|
+
require_relative "commands/ping_command"
|
172
|
+
require_relative "commands/quit_command"
|
173
|
+
require_relative "commands/scan_command"
|
174
|
+
require_relative "commands/instream_command"
|
175
|
+
require_relative "response"
|
176
|
+
require_relative "responses/error_response"
|
177
|
+
require_relative "responses/success_response"
|
178
|
+
require_relative "responses/virus_response"
|
179
|
+
require_relative "util"
|
180
|
+
require_relative "wrapper"
|
181
|
+
require_relative "wrappers/new_line_wrapper"
|
182
|
+
require_relative "wrappers/null_termination_wrapper"
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# clamav-client - ClamAV client
|
2
|
+
# Copyright (C) 2014 Franck Verrot <franck@verrot.fr>
|
3
|
+
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
module ClamAV
|
18
|
+
module Commands
|
19
|
+
class Command
|
20
|
+
|
21
|
+
def call; raise NotImplementedError.new; end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
# OK response looks like "1: stream: OK" or "1: /tmp/NOT_A_VIRUS.TXT: OK"
|
26
|
+
# FOUND response looks like "1: stream: Eicar-Test-Signature FOUND" or "1: /tmp/EICAR.COM: Eicar-Test-Signature FOUND"
|
27
|
+
def get_status_from_response(str)
|
28
|
+
/^(?<id>\d+): (?<filepath>.*): (?<virus_name>.*)\s?(?<status>(OK|FOUND))$/ =~ str
|
29
|
+
case status
|
30
|
+
when 'OK' then ClamAV::SuccessResponse.new(filepath)
|
31
|
+
when 'FOUND' then ClamAV::VirusResponse.new(filepath, virus_name.strip)
|
32
|
+
else ClamAV::ErrorResponse.new(str)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# clamav-client - ClamAV client
|
2
|
+
# Copyright (C) 2014 Franck Verrot <franck@verrot.fr>
|
3
|
+
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
module ClamAV
|
18
|
+
module Commands
|
19
|
+
class InstreamCommand < Command
|
20
|
+
|
21
|
+
def initialize(io, max_chunk_size = 1024)
|
22
|
+
@io = begin io rescue raise ArgumentError, 'io is required', caller; end
|
23
|
+
@max_chunk_size = max_chunk_size
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(conn)
|
27
|
+
conn.write_request("INSTREAM")
|
28
|
+
|
29
|
+
while(packet = @io.read(@max_chunk_size))
|
30
|
+
packet_size = [packet.size].pack("N")
|
31
|
+
conn.raw_write("#{packet_size}#{packet}")
|
32
|
+
end
|
33
|
+
conn.raw_write("\x00\x00\x00\x00")
|
34
|
+
get_status_from_response(conn.read_response)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# clamav-client - ClamAV client
|
2
|
+
# Copyright (C) 2014 Franck Verrot <franck@verrot.fr>
|
3
|
+
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
module ClamAV
|
18
|
+
module Commands
|
19
|
+
class PingCommand < Command
|
20
|
+
def call(conn)
|
21
|
+
!!(/\d+: PONG/.match(conn.send_request("PING")))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# clamav-client - ClamAV client
|
2
|
+
# Copyright (C) 2014 Franck Verrot <franck@verrot.fr>
|
3
|
+
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
module ClamAV
|
18
|
+
module Commands
|
19
|
+
class QuitCommand < Command
|
20
|
+
def call(conn)
|
21
|
+
conn.send_request("QUIT").nil?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# clamav-client - ClamAV client
|
2
|
+
# Copyright (C) 2014 Franck Verrot <franck@verrot.fr>
|
3
|
+
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
module ClamAV
|
18
|
+
module Commands
|
19
|
+
class ScanCommand < Command
|
20
|
+
|
21
|
+
def initialize(path, path_finder = Util)
|
22
|
+
@path, @path_finder = path, path_finder
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(conn)
|
26
|
+
@path_finder.path_to_files(@path).map { |file| scan_file(conn, file) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def scan_file(conn, file)
|
30
|
+
get_status_from_response(conn.send_request("SCAN #{file}"))
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|