clamav-client 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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +674 -0
- data/README.md +120 -0
- data/Rakefile +104 -0
- data/clamav-client.gemspec +22 -0
- data/lib/clamav/client.rb +43 -0
- data/lib/clamav/commands/command.rb +32 -0
- data/lib/clamav/commands/ping_command.rb +26 -0
- data/lib/clamav/commands/quit_command.rb +26 -0
- data/lib/clamav/commands/scan_command.rb +54 -0
- data/lib/clamav/connection.rb +37 -0
- data/lib/clamav/responses.rb +30 -0
- data/lib/clamav/responses/error_response.rb +21 -0
- data/lib/clamav/responses/success_response.rb +21 -0
- data/lib/clamav/responses/virus_response.rb +21 -0
- data/lib/clamav/util.rb +32 -0
- data/lib/clamav/wrapper.rb +22 -0
- data/lib/clamav/wrappers/new_line_wrapper.rb +35 -0
- data/lib/clamav/wrappers/null_termination_wrapper.rb +35 -0
- data/test/fixtures/clamavtest.gz +0 -0
- data/test/fixtures/clamavtest.txt +1 -0
- data/test/fixtures/clamavtest.zip +0 -0
- data/test/fixtures/innocent.txt +1 -0
- data/test/integration/clamav/client_test.rb +57 -0
- data/test/integration/clamav/util_test.rb +34 -0
- data/test/test_helper.rb +19 -0
- data/test/unit/clamav/client_test.rb +36 -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 +32 -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 +132 -0
data/README.md
ADDED
@@ -0,0 +1,120 @@
|
|
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 'clamav-client', require: 'clamav/client'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install clamav-client
|
18
|
+
|
19
|
+
Alternatively, you can spawn a `pry` console right away by just running:
|
20
|
+
|
21
|
+
$ rake console
|
22
|
+
|
23
|
+
## Requirements
|
24
|
+
|
25
|
+
* Ruby >= 2 (and Ruby >= 2.1 to be able to contribute to the project as it's making use of required keyword arguments)
|
26
|
+
* clamd
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
The `ClamAV::Client` class is responsible for opening a connection to a remote
|
31
|
+
ClamAV clam daemon.
|
32
|
+
|
33
|
+
You will find below the implemented commands.
|
34
|
+
|
35
|
+
### PING => Boolean
|
36
|
+
|
37
|
+
Pings the daemon to check whether it is alive.
|
38
|
+
|
39
|
+
client = ClamAV::Client.new
|
40
|
+
client.execute(ClamAV::Commands::PingCommand.new)
|
41
|
+
=> true
|
42
|
+
|
43
|
+
### SCAN <file_or_directory> => Array[Response]
|
44
|
+
|
45
|
+
Scans a file or a directory for the existence of a virus.
|
46
|
+
|
47
|
+
The absolute path must be given to that command.
|
48
|
+
|
49
|
+
client = ClamAV::Client.new
|
50
|
+
|
51
|
+
client.execute(ClamAV::Commands::ScanCommand.new('/tmp/path/foo.c')
|
52
|
+
=> [#<ClamAV::SuccessResponse:0x007fbf314b9478 @file="/tmp/foo.c">]
|
53
|
+
|
54
|
+
client.execute(ClamAV::Commands::ScanCommand.new('/tmp/path')
|
55
|
+
=> [#<ClamAV::SuccessResponse:0x007fc30c273298 @file="/tmp/path/foo.c">,
|
56
|
+
#<ClamAV::SuccessResponse:0x007fc30c272910 @file="/tmp/path/foo.cpp">]
|
57
|
+
|
58
|
+
|
59
|
+
### Custom commands
|
60
|
+
|
61
|
+
Custom commands can be given to the client. The contract between the client
|
62
|
+
and the command is thru the `call` method call. The `call` method is being
|
63
|
+
passed a `Connection` object.
|
64
|
+
|
65
|
+
Here's a simple example that implements the `VERSION` command:
|
66
|
+
|
67
|
+
# Build the client
|
68
|
+
client = ClamAV::Client.new
|
69
|
+
|
70
|
+
# Create the command lambda
|
71
|
+
version_command = lambda { |conn| conn.send_request("VERSION") }
|
72
|
+
=> #<Proc:0x007fc0d0c14b28>
|
73
|
+
|
74
|
+
# Execute the command
|
75
|
+
client.execute(version_command)
|
76
|
+
=> "1: ClamAV 0.98.1/18489/Tue Feb 18 16:00:05 2014"
|
77
|
+
|
78
|
+
|
79
|
+
## Defaults
|
80
|
+
|
81
|
+
The default values in use are:
|
82
|
+
|
83
|
+
* clamd socket: UNIX Socket, located at `/tmp/clamd/socket`;
|
84
|
+
* New-line terminated commands.
|
85
|
+
|
86
|
+
These defaults can be changed by injecting new defaults.
|
87
|
+
|
88
|
+
## Injecting dependencies
|
89
|
+
|
90
|
+
### Client
|
91
|
+
|
92
|
+
The main object is the `Client` object. It is responsible for executing the commands.
|
93
|
+
This object can receive a custom connection object.
|
94
|
+
|
95
|
+
### Connection
|
96
|
+
|
97
|
+
The connection object is the bridge between the raw socket object and the
|
98
|
+
protocol being used between the client and the daemon.
|
99
|
+
|
100
|
+
`clamd` supports two kinds of delimiters:
|
101
|
+
|
102
|
+
* NULL terminated commands
|
103
|
+
* New-line terminated commands
|
104
|
+
|
105
|
+
The management of those delimiters is done with the wrapper argument.
|
106
|
+
|
107
|
+
### Wrapper
|
108
|
+
|
109
|
+
The wrapper is responsible for taking the incoming request with the
|
110
|
+
`wrap_request` method, and parsing the response with the `read_response`
|
111
|
+
method.
|
112
|
+
|
113
|
+
|
114
|
+
## Contributing
|
115
|
+
|
116
|
+
1. Fork it ( https://github.com/franckverrot/clamav-client/fork )
|
117
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
118
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
119
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
120
|
+
5. Create new Pull Request
|
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,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
$:<< 'lib'
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = "clamav-client"
|
6
|
+
spec.version = "1.0.0"
|
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 = "GPLv3"
|
13
|
+
|
14
|
+
spec.files = `git ls-files -z`.split("\x0")
|
15
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
20
|
+
spec.add_development_dependency "rake"
|
21
|
+
spec.add_development_dependency "pry"
|
22
|
+
end
|
@@ -0,0 +1,43 @@
|
|
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 "clamav/connection"
|
18
|
+
require "clamav/commands/ping_command"
|
19
|
+
require "clamav/commands/quit_command"
|
20
|
+
require "clamav/commands/scan_command"
|
21
|
+
require "clamav/util"
|
22
|
+
require "clamav/wrappers/new_line_wrapper"
|
23
|
+
require "clamav/wrappers/null_termination_wrapper"
|
24
|
+
|
25
|
+
module ClamAV
|
26
|
+
class Client
|
27
|
+
def initialize(connection = default_connection)
|
28
|
+
@connection = connection
|
29
|
+
connection.establish_connection
|
30
|
+
end
|
31
|
+
|
32
|
+
def execute(command)
|
33
|
+
command.(@connection)
|
34
|
+
end
|
35
|
+
|
36
|
+
def default_connection
|
37
|
+
ClamAV::Connection.new(
|
38
|
+
socket: ::UNIXSocket.new('/tmp/clamd.socket'),
|
39
|
+
wrapper: ::ClamAV::Wrappers::NewLineWrapper.new
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,32 @@
|
|
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
|
+
module ClamAV
|
17
|
+
module Commands
|
18
|
+
class Command
|
19
|
+
def call
|
20
|
+
raise NotImplementedError.new
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
def execute
|
25
|
+
conn.puts data + "\n"
|
26
|
+
response = conn.gets.chomp
|
27
|
+
conn.close
|
28
|
+
response
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,26 @@
|
|
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
|
+
require 'clamav/commands/command'
|
17
|
+
|
18
|
+
module ClamAV
|
19
|
+
module Commands
|
20
|
+
class PingCommand < Command
|
21
|
+
def call(conn)
|
22
|
+
!!(/\d+: PONG/.match(conn.send_request("PING")))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
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
|
+
require 'clamav/commands/command'
|
17
|
+
|
18
|
+
module ClamAV
|
19
|
+
module Commands
|
20
|
+
class QuitCommand < Command
|
21
|
+
def call(conn)
|
22
|
+
conn.send_request("QUIT").nil?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,54 @@
|
|
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 'clamav/commands/command'
|
18
|
+
require 'clamav/responses/error_response'
|
19
|
+
require 'clamav/responses/success_response'
|
20
|
+
require 'clamav/responses/virus_response'
|
21
|
+
module ClamAV
|
22
|
+
module Commands
|
23
|
+
class ScanCommand < Command
|
24
|
+
Statuses = {
|
25
|
+
'ERROR' => ClamAV::ErrorResponse,
|
26
|
+
'OK' => ClamAV::SuccessResponse,
|
27
|
+
'ClamAV-Test-Signature FOUND' => ClamAV::VirusResponse
|
28
|
+
}
|
29
|
+
|
30
|
+
def initialize(path, path_finder = Util)
|
31
|
+
@path, @path_finder = path, path_finder
|
32
|
+
end
|
33
|
+
|
34
|
+
def call(conn)
|
35
|
+
@path_finder.path_to_files(@path).map { |file| scan_file(conn, file) }
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
def scan_file(conn, file)
|
40
|
+
get_status_from_response(conn.send_request("SCAN #{file}"))
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_status_from_response(str)
|
44
|
+
case str
|
45
|
+
when 'Error processing command. ERROR'
|
46
|
+
ErrorResponse.new(str)
|
47
|
+
else
|
48
|
+
/(?<id>\d+): (?<filepath>.*): (?<status>.*)/ =~ str
|
49
|
+
Statuses[status].new(filepath)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,37 @@
|
|
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
|
+
|
19
|
+
module ClamAV
|
20
|
+
class Connection
|
21
|
+
def initialize(socket:, wrapper:)
|
22
|
+
@socket = socket
|
23
|
+
@wrapper = wrapper
|
24
|
+
end
|
25
|
+
|
26
|
+
def establish_connection
|
27
|
+
wrapped_request = @wrapper.wrap_request("IDSESSION")
|
28
|
+
@socket.write wrapped_request
|
29
|
+
end
|
30
|
+
|
31
|
+
def send_request(str)
|
32
|
+
wrapped_request = @wrapper.wrap_request(str)
|
33
|
+
@socket.write wrapped_request
|
34
|
+
@wrapper.read_response(@socket)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|