op-clamav-client 3.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,129 @@
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 'timeout'
18
+
19
+ module ClamAV
20
+ class Connection
21
+ class ReadTimeoutError < ::ClamAV::Client::ReadTimeoutError; end
22
+ class WriteTimeoutError < ::ClamAV::Client::WriteTimeoutError; end
23
+
24
+ attr_accessor :client
25
+ attr_accessor :socket
26
+ attr_accessor :wrapper
27
+
28
+ def initialize(attrs={})
29
+ attrs.each do |attr, value|
30
+ send("#{attr}=", value)
31
+ end
32
+
33
+ begin
34
+ validate!
35
+ rescue => e
36
+ @client = nil
37
+ @socket = nil
38
+ @wrapper = nil
39
+
40
+ raise e
41
+ end
42
+ end
43
+
44
+ def validate!
45
+ missing_required_argument(:client) if !client
46
+ missing_required_argument(:socket) if !socket
47
+ missing_required_argument(:wrapper) if !wrapper
48
+ end
49
+
50
+ [
51
+ :tcp?,
52
+ :file,
53
+ :connect_timeout,
54
+ :read_timeout,
55
+ :write_timeout,
56
+ ].each do |m|
57
+ define_method(m) do
58
+ client.send(m)
59
+ end
60
+ end
61
+
62
+ def establish_connection
63
+ write_request("IDSESSION")
64
+ end
65
+
66
+ def write_request(str)
67
+ return write_request_with_timeout(str) if write_timeout
68
+
69
+ write_request_without_timeout(str)
70
+ end
71
+
72
+ def write_request_without_timeout(str)
73
+ wrapped_request = @wrapper.wrap_request(str)
74
+
75
+ @socket.write wrapped_request
76
+ end
77
+
78
+ def write_request_with_timeout(str)
79
+ Timeout::timeout(connect_timeout) do
80
+ write_request_without_timeout(str)
81
+ end
82
+ rescue Timeout::Error => e
83
+
84
+ raise WriteTimeoutError.new(e.to_s)
85
+ end
86
+
87
+ def read_response
88
+ return read_response_with_timeout if read_timeout
89
+
90
+ read_response_without_timeout
91
+ end
92
+
93
+ def read_response_without_timeout
94
+ @wrapper.read_response(@socket)
95
+ end
96
+
97
+ def read_response_with_timeout
98
+ Timeout::timeout(read_timeout) do
99
+ read_response_without_timeout
100
+ end
101
+ rescue Timeout::Error => e
102
+ raise ReadTimeoutError.new(e.to_s)
103
+ end
104
+
105
+ def disconnect!
106
+ return true if @socket.nil?
107
+
108
+ @socket.close
109
+
110
+ @socket.closed?.tap do
111
+ @socket = nil
112
+ end
113
+ end
114
+
115
+ def send_request(str)
116
+ write_request(str)
117
+ read_response
118
+ end
119
+
120
+ def raw_write(str)
121
+ @socket.write str
122
+ end
123
+
124
+ private
125
+ def missing_required_argument(key)
126
+ raise ArgumentError, "#{key} is required"
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,28 @@
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
+ class Response
19
+
20
+ attr_reader :file, :virus_name, :error_str
21
+
22
+ # Not sure if this is still required?
23
+ def ==(other)
24
+ @file == other.file && self.class == other.class
25
+ end
26
+
27
+ end
28
+ 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
+ class ErrorResponse < Response
19
+
20
+ def initialize(error_str)
21
+ @error_str = error_str
22
+ end
23
+
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
+ class SuccessResponse < Response
19
+
20
+ def initialize(file)
21
+ @file = file
22
+ end
23
+
24
+ end
25
+ 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
+
17
+ module ClamAV
18
+ class VirusResponse < Response
19
+
20
+ def initialize(file, virus_name)
21
+ @file = file
22
+ @virus_name = virus_name
23
+ end
24
+
25
+ end
26
+ 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
+
17
+ module ClamAV
18
+ module Util
19
+ UnknownPathException = Class.new(RuntimeError)
20
+
21
+ def Util.path_to_files(path)
22
+ if Dir.exist?(path)
23
+ Dir.glob(path + '/*')
24
+ elsif File.exist?(path)
25
+ [path]
26
+ else
27
+ message =" (path = #{path})"
28
+ raise UnknownPathException.new("#{__FILE__}:#{__LINE__} path_to_files : path argument neither a file nor a directory. Aborting. #{message}")
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
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
+ class Wrapper
19
+ def wrap_request(request); raise NotImplementedError; end
20
+ def unwrap_response(response); raise NotImplementedError; end
21
+
22
+ protected
23
+ def read_until(socket, delimiter)
24
+ buff = ""
25
+ while (char = socket.getc) != delimiter
26
+ buff << char
27
+ end
28
+ buff
29
+ rescue TypeError => e
30
+ raise ::ClamAV::Client::ConnectionError.new(e.to_s)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,29 @@
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 Wrappers
19
+ class NewLineWrapper < ::ClamAV::Wrapper
20
+ def wrap_request(request)
21
+ "n#{request}\n"
22
+ end
23
+
24
+ def read_response(socket)
25
+ read_until(socket, "\n")
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
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 Wrappers
19
+ class NullTerminationWrapper < ::ClamAV::Wrapper
20
+ def wrap_request(request)
21
+ "z#{request}\0"
22
+ end
23
+
24
+ def read_response(socket)
25
+ read_until(socket, "\0")
26
+ end
27
+ end
28
+ end
29
+ end
data/lib/clamav.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative 'clamav/client'
data/test/Dockerfile ADDED
@@ -0,0 +1,16 @@
1
+ ARG RUBY_VERSION
2
+ FROM ruby:${RUBY_VERSION}
3
+ LABEL maintainer="victor.pugin@lifen.fr"
4
+
5
+ WORKDIR /clamav-client
6
+ ADD Gemfile /clamav-client
7
+ ADD clamav-client.gemspec /clamav-client
8
+
9
+ RUN apt-get update -qq && \
10
+ apt-get install -y clamav-daemon clamav-freshclam clamav-unofficial-sigs && \
11
+ freshclam && \
12
+ bundle
13
+
14
+ ENTRYPOINT ["./start.sh"]
15
+
16
+ ADD . /clamav-client
@@ -0,0 +1,142 @@
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 'test_helper'
18
+
19
+ describe "ClamAV::Client Integration Tests" do
20
+ describe "default new line delimiter" do
21
+ let(:client) { ClamAV::Client.new }
22
+
23
+ describe "any callable" do
24
+ it "can be used" do
25
+ assert client.execute(lambda { |conn| true })
26
+ end
27
+ end
28
+
29
+ describe "ping" do
30
+ let(:ping_command) { ClamAV::Commands::PingCommand.new }
31
+
32
+ it "can be sent" do
33
+ assert client.execute(ping_command)
34
+ end
35
+
36
+ it "can be sent multiple times" do
37
+ assert client.execute(ping_command)
38
+ assert client.execute(ping_command)
39
+ end
40
+
41
+ it "can be used as #ping" do
42
+ assert_equal client.execute(ping_command), client.ping
43
+ end
44
+ end
45
+
46
+ describe "scan" do
47
+ let(:base_path) { File.expand_path('../../../../', __FILE__) }
48
+ let(:dir) { File.join(base_path, 'test/fixtures') }
49
+
50
+ it "can be started" do
51
+ results = client.execute(ClamAV::Commands::ScanCommand.new(dir))
52
+
53
+ expected_results = {
54
+ "#{base_path}/test/fixtures/clamavtest.gz" => ClamAV::VirusResponse,
55
+ "#{base_path}/test/fixtures/clamavtest.txt" => ClamAV::VirusResponse,
56
+ "#{base_path}/test/fixtures/clamavtest.zip" => ClamAV::VirusResponse,
57
+ "#{base_path}/test/fixtures/innocent.txt" => ClamAV::SuccessResponse
58
+ }
59
+
60
+ results.each do |result|
61
+ expected_result = expected_results[result.file]
62
+ assert_equal expected_result, result.class
63
+ end
64
+ end
65
+
66
+ it "can be used as #scan" do
67
+ assert_equal client.execute(ClamAV::Commands::ScanCommand.new(dir)), client.send(:scan, dir)
68
+ end
69
+
70
+ describe "with timeout configuration" do
71
+ let(:client) { ClamAV::Client.new(connect_timeout: 1, write_timeout: 1, read_timeout: 1) }
72
+
73
+ it "can be started" do
74
+ results = client.execute(ClamAV::Commands::ScanCommand.new(dir))
75
+
76
+ expected_results = {
77
+ "#{base_path}/test/fixtures/clamavtest.gz" => ClamAV::VirusResponse,
78
+ "#{base_path}/test/fixtures/clamavtest.txt" => ClamAV::VirusResponse,
79
+ "#{base_path}/test/fixtures/clamavtest.zip" => ClamAV::VirusResponse,
80
+ "#{base_path}/test/fixtures/innocent.txt" => ClamAV::SuccessResponse
81
+ }
82
+
83
+ results.each do |result|
84
+ expected_result = expected_results[result.file]
85
+ assert_equal expected_result, result.class
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ describe "instream" do
92
+ let(:dir) { File.expand_path('../../../../test/fixtures', __FILE__) }
93
+
94
+ it "can recognize a sane file" do
95
+ command = build_command_for_file('innocent.txt')
96
+ client.execute(command).must_equal ClamAV::SuccessResponse.new("stream")
97
+ end
98
+
99
+ it "can recognize an infected file" do
100
+ command = build_command_for_file('clamavtest.txt')
101
+ client.execute(command).must_equal ClamAV::VirusResponse.new("stream", "ClamAV-Test-Signature")
102
+ end
103
+
104
+ it "can be used as #instream" do
105
+ io = File.open(File.join(dir, 'innocent.txt'))
106
+ assert_equal client.execute(ClamAV::Commands::InstreamCommand.new(io)), client.send(:instream, io)
107
+ end
108
+
109
+ def build_command_for_file(file)
110
+ io = File.open(File.join(dir, file))
111
+ ClamAV::Commands::InstreamCommand.new(io)
112
+ end
113
+ end
114
+
115
+ describe 'safe?' do
116
+ let(:dir) { File.expand_path('../../../../test/fixtures', __FILE__) }
117
+
118
+ it 'returns true if the given io is safe' do
119
+ io = build_io_obj('innocent.txt')
120
+ assert client.safe?(io)
121
+ end
122
+
123
+ it 'returns false if the given io is infected' do
124
+ io = build_io_obj('clamavtest.txt')
125
+ refute client.safe?(io)
126
+ end
127
+
128
+ it 'returns false if there is any infected file in the given files' do
129
+ refute client.safe?(dir)
130
+ end
131
+
132
+ it 'returns true if all the give file is safe' do
133
+ assert client.safe?("#{dir}/innocent.txt")
134
+ end
135
+
136
+ def build_io_obj(file)
137
+ content = File.read(File.join(dir, file))
138
+ io = StringIO.new(content)
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,41 @@
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 'test_helper'
18
+ describe "ClamAV::Client Integration Tests" do
19
+ describe "Util" do
20
+ describe "absolute_path" do
21
+ it "transforms a single file to an array of one element" do
22
+ expected_path = File.absolute_path(__FILE__)
23
+ actual_path = ClamAV::Util.path_to_files(__FILE__).first
24
+ assert_equal expected_path, actual_path
25
+ end
26
+
27
+ it "transforms a directory to an array of N element" do
28
+ files = %w(
29
+ clamav-client/test/integration/clamav/client_test.rb
30
+ clamav-client/test/integration/clamav/util_test.rb
31
+ ).sort
32
+ actual_files = ClamAV::Util.path_to_files(File.dirname(__FILE__)).sort
33
+
34
+ files.each_with_index do |file, index|
35
+ path = Regexp.new(".*/#{file}")
36
+ assert path.match(actual_files[index])
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,21 @@
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
+ gem 'minitest'
18
+
19
+ require 'minitest/autorun'
20
+ require 'clamav/client'
21
+ require 'pry'
@@ -0,0 +1,59 @@
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 'test_helper'
18
+
19
+ describe "ClamAV::Client" do
20
+ describe "connect!" do
21
+ let(:conn_mock) { Minitest::Mock.new }
22
+ let(:client) { ClamAV::Client.new }
23
+
24
+ it "opens the connection" do
25
+ conn_mock.expect(:establish_connection, nil)
26
+
27
+ client.connect!(conn_mock)
28
+
29
+ conn_mock.verify
30
+ end
31
+
32
+ it 'raises an custom error if connection times out' do
33
+ conn_mock.expect(:establish_connection, nil) do
34
+ raise Errno::ETIMEDOUT
35
+ end
36
+
37
+ assert_raises(ClamAV::Client::ConnectTimeoutError) { client.connect!(conn_mock) }
38
+ end
39
+
40
+ it 'raises an custom error if something goes wrong' do
41
+ conn_mock.expect(:establish_connection, nil) do
42
+ raise SocketError
43
+ end
44
+
45
+ assert_raises(ClamAV::Client::ConnectionError) { client.connect!(conn_mock) }
46
+ end
47
+ end
48
+
49
+ describe "tcp?" do
50
+ it "returns true when config is tcp" do
51
+ assert client = ClamAV::Client.new(tcp_host: 'example', tcp_port: 3310).tcp?
52
+ end
53
+
54
+ it "returns false when config is not tcp" do
55
+ refute client = ClamAV::Client.new.tcp?
56
+ refute client = ClamAV::Client.new(unix_socket: '/some.sock').tcp?
57
+ end
58
+ end
59
+ end