op-clamav-client 3.4.2
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.
- 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
|