ruby_clamdscan 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.idea/.gitignore +8 -0
- data/.idea/modules.xml +8 -0
- data/.idea/ruby_clamdscan.iml +73 -0
- data/.idea/vcs.xml +6 -0
- data/.rspec +2 -0
- data/.rubocop.yml +37 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +63 -0
- data/LICENSE.txt +339 -0
- data/README.md +33 -0
- data/Rakefile +12 -0
- data/docker/clamav/config/clamd.conf +801 -0
- data/docker/clamav/config/freshclam.conf +204 -0
- data/docker-compose.yml +18 -0
- data/eicar.com +1 -0
- data/lib/ruby_clamdscan/commands/manage.rb +23 -0
- data/lib/ruby_clamdscan/commands/scan.rb +117 -0
- data/lib/ruby_clamdscan/commands/status.rb +36 -0
- data/lib/ruby_clamdscan/commands/utils.rb +39 -0
- data/lib/ruby_clamdscan/configuration.rb +52 -0
- data/lib/ruby_clamdscan/errors/clamav_errors.rb +44 -0
- data/lib/ruby_clamdscan/models/scan_result.rb +41 -0
- data/lib/ruby_clamdscan/socket.rb +31 -0
- data/lib/ruby_clamdscan/version.rb +5 -0
- data/lib/ruby_clamdscan.rb +62 -0
- data/sig/ruby_clamdscan.rbs +4 -0
- data/test_image.jpg +0 -0
- metadata +77 -0
@@ -0,0 +1,204 @@
|
|
1
|
+
##
|
2
|
+
## Example config file for freshclam
|
3
|
+
## Please read the freshclam.conf(5) manual before editing this file.
|
4
|
+
##
|
5
|
+
|
6
|
+
|
7
|
+
# Comment or remove the line below.
|
8
|
+
# Example
|
9
|
+
|
10
|
+
# Path to the database directory.
|
11
|
+
# WARNING: It must match clamd.conf's directive!
|
12
|
+
# Default: hardcoded (depends on installation options)
|
13
|
+
#DatabaseDirectory /var/lib/clamav
|
14
|
+
|
15
|
+
# Path to the log file (make sure it has proper permissions)
|
16
|
+
# Default: disabled
|
17
|
+
UpdateLogFile /var/log/clamav/freshclam.log
|
18
|
+
|
19
|
+
# Maximum size of the log file.
|
20
|
+
# Value of 0 disables the limit.
|
21
|
+
# You may use 'M' or 'm' for megabytes (1M = 1m = 1048576 bytes)
|
22
|
+
# and 'K' or 'k' for kilobytes (1K = 1k = 1024 bytes).
|
23
|
+
# in bytes just don't use modifiers. If LogFileMaxSize is enabled,
|
24
|
+
# log rotation (the LogRotate option) will always be enabled.
|
25
|
+
# Default: 1M
|
26
|
+
#LogFileMaxSize 2M
|
27
|
+
|
28
|
+
# Log time with each message.
|
29
|
+
# Default: no
|
30
|
+
#LogTime yes
|
31
|
+
|
32
|
+
# Enable verbose logging.
|
33
|
+
# Default: no
|
34
|
+
#LogVerbose yes
|
35
|
+
|
36
|
+
# Use system logger (can work together with UpdateLogFile).
|
37
|
+
# Default: no
|
38
|
+
#LogSyslog yes
|
39
|
+
|
40
|
+
# Specify the type of syslog messages - please refer to 'man syslog'
|
41
|
+
# for facility names.
|
42
|
+
# Default: LOG_LOCAL6
|
43
|
+
#LogFacility LOG_MAIL
|
44
|
+
|
45
|
+
# Enable log rotation. Always enabled when LogFileMaxSize is enabled.
|
46
|
+
# Default: no
|
47
|
+
#LogRotate yes
|
48
|
+
|
49
|
+
# This option allows you to save the process identifier of the daemon
|
50
|
+
# This file will be owned by root, as long as freshclam was started by root.
|
51
|
+
# It is recommended that the directory where this file is stored is
|
52
|
+
# also owned by root to keep other users from tampering with it.
|
53
|
+
# Default: disabled
|
54
|
+
PidFile /tmp/freshclam.pid
|
55
|
+
|
56
|
+
# By default when started freshclam drops privileges and switches to the
|
57
|
+
# "clamav" user. This directive allows you to change the database owner.
|
58
|
+
# Default: clamav (may depend on installation options)
|
59
|
+
DatabaseOwner clamav
|
60
|
+
|
61
|
+
# Use DNS to verify virus database version. FreshClam uses DNS TXT records
|
62
|
+
# to verify database and software versions. With this directive you can change
|
63
|
+
# the database verification domain.
|
64
|
+
# WARNING: Do not touch it unless you're configuring freshclam to use your
|
65
|
+
# own database verification domain.
|
66
|
+
# Default: current.cvd.clamav.net
|
67
|
+
#DNSDatabaseInfo current.cvd.clamav.net
|
68
|
+
|
69
|
+
# database.clamav.net is now the primary domain name to be used world-wide.
|
70
|
+
# Now that CloudFlare is being used as our Content Delivery Network (CDN),
|
71
|
+
# this one domain name works world-wide to direct freshclam to the closest
|
72
|
+
# geographic endpoint.
|
73
|
+
# If the old db.XY.clamav.net domains are set, freshclam will automatically
|
74
|
+
# use database.clamav.net instead.
|
75
|
+
DatabaseMirror database.clamav.net
|
76
|
+
|
77
|
+
# How many attempts to make before giving up.
|
78
|
+
# Default: 3 (per mirror)
|
79
|
+
#MaxAttempts 5
|
80
|
+
|
81
|
+
# With this option you can control scripted updates. It's highly recommended
|
82
|
+
# to keep it enabled.
|
83
|
+
# Default: yes
|
84
|
+
ScriptedUpdates yes
|
85
|
+
|
86
|
+
# By default freshclam will keep the local databases (.cld) uncompressed to
|
87
|
+
# make their handling faster. With this option you can enable the compression;
|
88
|
+
# the change will take effect with the next database update.
|
89
|
+
# Default: no
|
90
|
+
#CompressLocalDatabase no
|
91
|
+
|
92
|
+
# With this option you can provide custom sources for database files.
|
93
|
+
# This option can be used multiple times. Support for:
|
94
|
+
# http(s)://, ftp(s)://, or file://
|
95
|
+
# Default: no custom URLs
|
96
|
+
#DatabaseCustomURL http://myserver.example.com/mysigs.ndb
|
97
|
+
#DatabaseCustomURL https://myserver.example.com/mysigs.ndb
|
98
|
+
#DatabaseCustomURL https://myserver.example.com:4567/allow_list.wdb
|
99
|
+
#DatabaseCustomURL ftp://myserver.example.com/example.ldb
|
100
|
+
#DatabaseCustomURL ftps://myserver.example.com:4567/example.ndb
|
101
|
+
#DatabaseCustomURL file:///mnt/nfs/local.hdb
|
102
|
+
|
103
|
+
# This option allows you to easily point freshclam to private mirrors.
|
104
|
+
# If PrivateMirror is set, freshclam does not attempt to use DNS
|
105
|
+
# to determine whether its databases are out-of-date, instead it will
|
106
|
+
# use the If-Modified-Since request or directly check the headers of the
|
107
|
+
# remote database files. For each database, freshclam first attempts
|
108
|
+
# to download the CLD file. If that fails, it tries to download the
|
109
|
+
# CVD file. This option overrides DatabaseMirror, DNSDatabaseInfo
|
110
|
+
# and ScriptedUpdates. It can be used multiple times to provide
|
111
|
+
# fall-back mirrors.
|
112
|
+
# Default: disabled
|
113
|
+
#PrivateMirror mirror1.example.com
|
114
|
+
#PrivateMirror mirror2.example.com
|
115
|
+
|
116
|
+
# Number of database checks per day.
|
117
|
+
# Default: 12 (every two hours)
|
118
|
+
#Checks 24
|
119
|
+
|
120
|
+
# Proxy settings
|
121
|
+
# The HTTPProxyServer may be prefixed with [scheme]:// to specify which kind
|
122
|
+
# of proxy is used.
|
123
|
+
# http:// HTTP Proxy. Default when no scheme or proxy type is specified.
|
124
|
+
# https:// HTTPS Proxy. (Added in 7.52.0 for OpenSSL, GnuTLS and NSS)
|
125
|
+
# socks4:// SOCKS4 Proxy.
|
126
|
+
# socks4a:// SOCKS4a Proxy. Proxy resolves URL hostname.
|
127
|
+
# socks5:// SOCKS5 Proxy.
|
128
|
+
# socks5h:// SOCKS5 Proxy. Proxy resolves URL hostname.
|
129
|
+
# Default: disabled
|
130
|
+
#HTTPProxyServer https://proxy.example.com
|
131
|
+
#HTTPProxyPort 1234
|
132
|
+
#HTTPProxyUsername myusername
|
133
|
+
#HTTPProxyPassword mypass
|
134
|
+
|
135
|
+
# If your servers are behind a firewall/proxy which applies User-Agent
|
136
|
+
# filtering you can use this option to force the use of a different
|
137
|
+
# User-Agent header.
|
138
|
+
# As of ClamAV 0.103.3, this setting may not be used when updating from the
|
139
|
+
# clamav.net CDN and can only be used when updating from a private mirror.
|
140
|
+
# Default: clamav/version_number (OS: ..., ARCH: ..., CPU: ..., UUID: ...)
|
141
|
+
#HTTPUserAgent SomeUserAgentIdString
|
142
|
+
|
143
|
+
# Use aaa.bbb.ccc.ddd as client address for downloading databases. Useful for
|
144
|
+
# multi-homed systems.
|
145
|
+
# Default: Use OS'es default outgoing IP address.
|
146
|
+
#LocalIPAddress aaa.bbb.ccc.ddd
|
147
|
+
|
148
|
+
# Send the RELOAD command to clamd.
|
149
|
+
# Default: no
|
150
|
+
NotifyClamd /etc/clamav/clamd.conf
|
151
|
+
|
152
|
+
# Run command after successful database update.
|
153
|
+
# Use EXIT_1 to return 1 after successful database update.
|
154
|
+
# Default: disabled
|
155
|
+
#OnUpdateExecute command
|
156
|
+
|
157
|
+
# Run command when database update process fails.
|
158
|
+
# Default: disabled
|
159
|
+
#OnErrorExecute command
|
160
|
+
|
161
|
+
# Run command when freshclam reports outdated version.
|
162
|
+
# In the command string %v will be replaced by the new version number.
|
163
|
+
# Default: disabled
|
164
|
+
#OnOutdatedExecute command
|
165
|
+
|
166
|
+
# Don't fork into background.
|
167
|
+
# Default: no
|
168
|
+
#Foreground yes
|
169
|
+
|
170
|
+
# Enable debug messages in libclamav.
|
171
|
+
# Default: no
|
172
|
+
#Debug yes
|
173
|
+
|
174
|
+
# Timeout in seconds when connecting to database server.
|
175
|
+
# Default: 30
|
176
|
+
#ConnectTimeout 60
|
177
|
+
|
178
|
+
# Timeout in seconds when reading from database server. 0 means no timeout.
|
179
|
+
# Default: 60
|
180
|
+
#ReceiveTimeout 300
|
181
|
+
|
182
|
+
# With this option enabled, freshclam will attempt to load new databases into
|
183
|
+
# memory to make sure they are properly handled by libclamav before replacing
|
184
|
+
# the old ones.
|
185
|
+
# Tip: This feature uses a lot of RAM. If your system has limited RAM and you
|
186
|
+
# are actively running ClamD or ClamScan during the update, then you may need
|
187
|
+
# to set `TestDatabases no`.
|
188
|
+
# Default: yes
|
189
|
+
#TestDatabases no
|
190
|
+
|
191
|
+
# This option enables downloading of bytecode.cvd, which includes additional
|
192
|
+
# detection mechanisms and improvements to the ClamAV engine.
|
193
|
+
# Default: yes
|
194
|
+
#Bytecode no
|
195
|
+
|
196
|
+
# Include an optional signature databases (opt-in).
|
197
|
+
# This option can be used multiple times.
|
198
|
+
#ExtraDatabase dbname1
|
199
|
+
#ExtraDatabase dbname2
|
200
|
+
|
201
|
+
# Exclude a standard signature database (opt-out).
|
202
|
+
# This option can be used multiple times.
|
203
|
+
#ExcludeDatabase dbname1
|
204
|
+
#ExcludeDatabase dbname2
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
version: "3.9"
|
2
|
+
services:
|
3
|
+
clamav:
|
4
|
+
image: clamav/clamav:latest
|
5
|
+
ports:
|
6
|
+
- "3310:3310"
|
7
|
+
volumes:
|
8
|
+
- clamav-dev:/var/lib/clamav # Virus database
|
9
|
+
- ./docker/clamav/config/clamd.conf:/etc/clamav/clamd.conf
|
10
|
+
- ./docker/clamav/config/freshclam.conf:/etc/clamav/freshclam.conf
|
11
|
+
|
12
|
+
volumes:
|
13
|
+
clamav-dev:
|
14
|
+
|
15
|
+
networks:
|
16
|
+
ruby-clamdscan:
|
17
|
+
external: true
|
18
|
+
name: ruby-clamdscan
|
data/eicar.com
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ruby_clamdscan/commands/utils"
|
4
|
+
|
5
|
+
module RubyClamdscan
|
6
|
+
module Commands
|
7
|
+
# Management commands for
|
8
|
+
module Manage
|
9
|
+
# Force ClamAV to reload the virus databases
|
10
|
+
# @param configuration [RubyClamdscan::Configuration] configuration for building the ClamAV connection
|
11
|
+
def self.reload_server_database(configuration)
|
12
|
+
RubyClamdscan::Commands::Utils.send_single_command("RELOAD", configuration)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Shutdown ClamAV server
|
16
|
+
# Note: this will completely close socket communication. Server cannot be restarted through this library
|
17
|
+
# @param configuration [RubyClamdscan::Configuration] configuration for building the ClamAV connection
|
18
|
+
def self.shutdown_server(configuration)
|
19
|
+
RubyClamdscan::Commands::Utils.send_single_command("SHUTDOWN", configuration)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "socket"
|
4
|
+
require "ruby_clamdscan/models/scan_result"
|
5
|
+
require "ruby_clamdscan/errors/clamav_errors"
|
6
|
+
|
7
|
+
module RubyClamdscan
|
8
|
+
module Commands
|
9
|
+
# Methods for scanning file contents
|
10
|
+
module Scan
|
11
|
+
SCAN_COMMAND = "zINSTREAM\0"
|
12
|
+
|
13
|
+
# Stream file contents to ClamAV
|
14
|
+
# @param file_input_stream [IO] Input file_input_stream to scan
|
15
|
+
# @param configuration [RubyClamdscan::Configuration] Configuration settings
|
16
|
+
# @return [RubyClamdscan::Models::ScanResult]
|
17
|
+
# @raise [RubyClamdscan::Errors::VirusDetectedError] if Configuration is set to raise exception and malware is detected
|
18
|
+
# @raise [RubyClamdscan::Errors::ClamAVCommunicationError] if communication with ClamAV server fails
|
19
|
+
def self.scan(file_input_stream, configuration)
|
20
|
+
response = ""
|
21
|
+
clam_av_stream = nil
|
22
|
+
|
23
|
+
begin
|
24
|
+
clam_av_stream = RubyClamdscan::Socket.open_clamav_socket(configuration)
|
25
|
+
send_contents(clam_av_stream, configuration, file_input_stream)
|
26
|
+
response = get_response(clam_av_stream)
|
27
|
+
rescue StandardError => e
|
28
|
+
raise RubyClamdscan::Errors::ClamAVCommunicationError.new(SCAN_COMMAND, e)
|
29
|
+
ensure
|
30
|
+
clam_av_stream&.close
|
31
|
+
end
|
32
|
+
|
33
|
+
result = build_result(response)
|
34
|
+
|
35
|
+
raise RubyClamdscan::Errors::VirusDetectedError, result if configuration.raise_error_on_virus_detected && result.contains_virus
|
36
|
+
|
37
|
+
result
|
38
|
+
end
|
39
|
+
|
40
|
+
# Stream file contents to ClamAV
|
41
|
+
# @param filepath [String] Path to file in local storage to scan
|
42
|
+
# @param configuration [RubyClamdscan::Configuration] Configuration settings
|
43
|
+
# @return [RubyClamdscan::Models::ScanResult]
|
44
|
+
# @raise IOError if there is no file found at filepath
|
45
|
+
def self.scan_file(filepath, configuration)
|
46
|
+
raise IOError, "Unable to find file #{filepath}" unless File.exist?(filepath)
|
47
|
+
|
48
|
+
begin
|
49
|
+
fd = IO.sysopen(filepath, "rb")
|
50
|
+
fin = IO.new(fd)
|
51
|
+
result = scan(fin, configuration)
|
52
|
+
ensure
|
53
|
+
fin.close
|
54
|
+
end
|
55
|
+
result
|
56
|
+
end
|
57
|
+
|
58
|
+
# Builds a result object after parsing the response from ClamAV
|
59
|
+
# @param response [String] Response from ClamAV stream
|
60
|
+
# @return [RubyClamdscan::Models::ScanResult] Constructed result object
|
61
|
+
def self.build_result(response)
|
62
|
+
# OK response: "stream: OK"
|
63
|
+
# Malware response: "stream: Win.Test.EICAR_HDB-1 FOUND"
|
64
|
+
# Error response "stream: <message> ERROR"
|
65
|
+
|
66
|
+
response = response.strip # Strip out any trailing empty chars from the buffer
|
67
|
+
tokens = response.split(" ")
|
68
|
+
puts("RESPONSE:'#{response}'")
|
69
|
+
|
70
|
+
case tokens
|
71
|
+
in ["stream:", "OK"]
|
72
|
+
RubyClamdscan::Models::ScanResult.new(is_successful: true, contains_virus: false)
|
73
|
+
in ["stream:", virus_info, "FOUND"]
|
74
|
+
RubyClamdscan::Models::ScanResult.new(is_successful: true, contains_virus: true, virus_info:)
|
75
|
+
in []
|
76
|
+
RubyClamdscan::Models::ScanResult.new(is_successful: false, contains_virus: nil,
|
77
|
+
error_message: "Empty response from ClamAV, is the server accepting connections?")
|
78
|
+
else
|
79
|
+
RubyClamdscan::Models::ScanResult.new(is_successful: false, contains_virus: nil, error_message: response)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private_class_method :build_result
|
84
|
+
|
85
|
+
# Gets the response from ClamAV off the stream
|
86
|
+
# @param clam_av_stream [IO] Read stream from ClamAV
|
87
|
+
# @return [String] Response
|
88
|
+
def self.get_response(clam_av_stream)
|
89
|
+
response = ""
|
90
|
+
while (data = clam_av_stream.gets)
|
91
|
+
response += data
|
92
|
+
end
|
93
|
+
response
|
94
|
+
end
|
95
|
+
|
96
|
+
private_class_method :get_response
|
97
|
+
|
98
|
+
# Sends file contents from stream to ClamAV
|
99
|
+
# @param clam_av_stream [IO] Stream to ClamAV
|
100
|
+
# @param configuration [RubyClamdscan::Configuration] Params defining how much data to send per chunk
|
101
|
+
# @param file_input_stream [IO] Stream to read file contents from
|
102
|
+
def self.send_contents(clam_av_stream, configuration, file_input_stream)
|
103
|
+
clam_av_stream.write(SCAN_COMMAND) # Write the command to tell ClamAV to start scanning
|
104
|
+
clam_av_stream.flush
|
105
|
+
while (chunk = file_input_stream.read(configuration.chunk_size))
|
106
|
+
chunk_len = [chunk.length].pack("N")
|
107
|
+
clam_av_stream.write(chunk_len + chunk)
|
108
|
+
clam_av_stream.flush
|
109
|
+
end
|
110
|
+
clam_av_stream.write([0x00, 0x00, 0x00, 0x00].pack("NNNN"))
|
111
|
+
clam_av_stream.flush
|
112
|
+
end
|
113
|
+
|
114
|
+
private_class_method :send_contents
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ruby_clamdscan/commands/utils"
|
4
|
+
|
5
|
+
module RubyClamdscan
|
6
|
+
module Commands
|
7
|
+
# Status commands for ClamAV
|
8
|
+
module Status
|
9
|
+
# Attempts to ping the ClamAV server
|
10
|
+
# @param configuration [RubyClamdscan::Configuration] configuration for building the ClamAV connection
|
11
|
+
# @raise [RubyClamdscan::Exceptions::EmptyResponseError] If server response is empty
|
12
|
+
def self.ping_server(configuration)
|
13
|
+
RubyClamdscan::Commands::Utils.send_single_command("PING", configuration)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Attempts to retrieve the ClamAV server's version information
|
17
|
+
# @param configuration [RubyClamdscan::Configuration] configuration for building the ClamAV connection
|
18
|
+
# @raise [RubyClamdscan::Exceptions::EmptyResponseError] If server response is empty
|
19
|
+
def self.server_version(configuration)
|
20
|
+
RubyClamdscan::Commands::Utils.send_single_command("VERSION", configuration)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Replies with statistics about the scan queue, contents of scan queue, and memory usage
|
24
|
+
# Because the format of this response is subject to change, this method will only return the string
|
25
|
+
# Uses "nSTATS\n", blocks in the returned response will be separated by the \n character
|
26
|
+
# @param configuration [RubyClamdscan::Configuration] configuration for building the ClamAV connection
|
27
|
+
# @return [String] Format (currently):
|
28
|
+
# "POOLS: 1\n\nSTATE: VALID PRIMARY\nTHREADS: live 1 idle 0 max 10 idle-timeout 30\nQUEUE: 0 items\n\tSTATS 0.000375 \n\n
|
29
|
+
# MEMSTATS: heap N/A mmap N/A used N/A free N/A releasable N/A pools 1 pools_used 1281.773M pools_total 1281.827M\nEND"
|
30
|
+
# @raise [RubyClamdscan::Errors::EmptyResponseError] If server response is empty
|
31
|
+
def self.server_stats(configuration)
|
32
|
+
RubyClamdscan::Commands::Utils.send_single_command("nSTATS\n", configuration)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ruby_clamdscan/errors/clamav_errors"
|
4
|
+
|
5
|
+
module RubyClamdscan
|
6
|
+
module Commands
|
7
|
+
# Utilities for running commands
|
8
|
+
module Utils
|
9
|
+
# Sends a single command to the ClamAV server and return its response
|
10
|
+
# @param command [String] Command to send
|
11
|
+
# @param configuration [RubyClamdscan::Configuration] Configuration used for building socket
|
12
|
+
# @return [String] Response from ClamAV server
|
13
|
+
# @raise [RubyClamdscan::Errors::EmptyResponseError] if configuration.raise_error_on_empty_response is true and server doesn't send response
|
14
|
+
# @raise
|
15
|
+
def self.send_single_command(command, configuration)
|
16
|
+
response = ""
|
17
|
+
begin
|
18
|
+
clam_av_stream = RubyClamdscan::Socket.open_clamav_socket(configuration)
|
19
|
+
clam_av_stream.write(command)
|
20
|
+
clam_av_stream.flush
|
21
|
+
|
22
|
+
while (data = clam_av_stream.gets)
|
23
|
+
response += data
|
24
|
+
end
|
25
|
+
|
26
|
+
response = response.strip
|
27
|
+
rescue StandardError => e
|
28
|
+
raise RubyClamdscan::Errors::ClamAVCommunicationError.new(command, e)
|
29
|
+
ensure
|
30
|
+
clam_av_stream&.close
|
31
|
+
end
|
32
|
+
|
33
|
+
raise RubyClamdscan::Errors::EmptyResponseError, command if configuration.raise_error_on_empty_response && response.empty?
|
34
|
+
|
35
|
+
response
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyClamdscan
|
4
|
+
# Configuration for the gem
|
5
|
+
class Configuration
|
6
|
+
# TCP Port ClamAV is listening on if using TCP
|
7
|
+
# Default: 3310
|
8
|
+
# @return [Integer]
|
9
|
+
attr_accessor :tcp_port
|
10
|
+
|
11
|
+
# Host ClamAV is listening on if using TCP
|
12
|
+
# Default: localhost
|
13
|
+
# @return [String]
|
14
|
+
attr_accessor :tcp_host
|
15
|
+
|
16
|
+
# Unix Socket for ClamAV
|
17
|
+
# Default: /tmp/clamd.socket
|
18
|
+
# @return [String]
|
19
|
+
attr_accessor :unix_socket
|
20
|
+
|
21
|
+
# Chunk size in bytes for streaming files
|
22
|
+
# Default: 1024
|
23
|
+
# @return [Integer]
|
24
|
+
attr_accessor :chunk_size
|
25
|
+
|
26
|
+
# If TCP socket should be used. If false, the Unix socket will be used instead
|
27
|
+
# Note, if running ClamAV on the same host, it is recommended to use the Unix socket as it's much faster
|
28
|
+
# Default: false
|
29
|
+
# @return [Boolean]
|
30
|
+
attr_accessor :use_tcp_socket
|
31
|
+
|
32
|
+
# If the server responds with an empty string, raise an error instead of just returning the empty string
|
33
|
+
# Default: true
|
34
|
+
# @return [Boolean]
|
35
|
+
attr_accessor :raise_error_on_empty_response
|
36
|
+
|
37
|
+
# If a virus is detected in the scanned contents, raise an error
|
38
|
+
# RubyClamdscan::Commands::Scan methods will raise RubyClamdscan::Errors::VirusDetected
|
39
|
+
# Default: true
|
40
|
+
# @return [Boolean]
|
41
|
+
attr_accessor :raise_error_on_virus_detected
|
42
|
+
|
43
|
+
def initialize
|
44
|
+
@tcp_host = "localhost"
|
45
|
+
@tcp_port = 3310
|
46
|
+
@chunk_size = 1024
|
47
|
+
@unix_socket = "/tmp/clamd.socket"
|
48
|
+
@raise_error_on_empty_response = true
|
49
|
+
@raise_error_on_virus_detected = true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyClamdscan
|
4
|
+
module Errors
|
5
|
+
# Raised when ClamAV returns an empty response - usually means that the server has not finished starting or is shutdown
|
6
|
+
class EmptyResponseError < StandardError
|
7
|
+
# @param command [String] Command that failed
|
8
|
+
# @param msg [String, nil] Optional message
|
9
|
+
def initialize(command, msg: nil)
|
10
|
+
msg = "Got empty response when attempting to run command: #{command}. Are you sure the ClamAV server is running?" unless msg.nil?
|
11
|
+
super(msg)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Raised when there is some error in communicating with the ClamAV server
|
16
|
+
# @param command [String] Command that failed
|
17
|
+
# @param cause [StandardError, nil] Underlying error if found
|
18
|
+
# @param msg [String, nil] Optional message
|
19
|
+
class ClamAVCommunicationError < StandardError
|
20
|
+
# @return [StandardError, nil] Underlying error if found
|
21
|
+
attr_reader :cause
|
22
|
+
|
23
|
+
def initialize(command, cause, msg: nil)
|
24
|
+
@cause = cause
|
25
|
+
msg = "Error while communicating with ClamAV - cause: #{cause&.msg} command: #{command}." unless msg.nil?
|
26
|
+
super(msg)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Raised if ClamAV detects malware in the scanned contents
|
31
|
+
class VirusDetectedError < StandardError
|
32
|
+
# @return [RubyClamdscan::Models::ScanResult] Information about scan and virus
|
33
|
+
attr_reader :scan_result
|
34
|
+
|
35
|
+
# @param scan_result [RubyClamdscan::Models::ScanResult] Information about scan result
|
36
|
+
# @param msg [String, nil] Optional message
|
37
|
+
def initialize(scan_result, msg: nil)
|
38
|
+
@scan_result = scan_result
|
39
|
+
msg = "Detected malware in scanned contents: #{scan_result.virus_info}" unless msg.nil?
|
40
|
+
super(msg)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyClamdscan
|
4
|
+
module Models
|
5
|
+
# Represents the result from scanning a file with ClamAV
|
6
|
+
class ScanResult
|
7
|
+
# If the command successfully - does not indicate file was safe! Check contains_virus for that information
|
8
|
+
# @return [Boolean]
|
9
|
+
attr_reader :is_successful
|
10
|
+
|
11
|
+
# If ClamAV detected malware in the passed file, will be nil if there was an error running the command
|
12
|
+
# @return [Boolean, nil]
|
13
|
+
attr_reader :contains_virus
|
14
|
+
|
15
|
+
# Returns the error string if the command failed
|
16
|
+
# @return [Exception, nil]
|
17
|
+
attr_reader :exception
|
18
|
+
|
19
|
+
# Returns the error string if the command failed
|
20
|
+
# @return [String, nil]
|
21
|
+
attr_reader :error_message
|
22
|
+
|
23
|
+
# Returns virus information if a virus was detected
|
24
|
+
# Value is the response from ClamAV, the malware classification - should be in the form of "Win.Test.EICAR_HDB-1"
|
25
|
+
# @return [String, nil]
|
26
|
+
attr_reader :virus_info
|
27
|
+
|
28
|
+
def initialize(is_successful:, contains_virus:, virus_info: nil, exception: nil, error_message: nil)
|
29
|
+
@is_successful = is_successful
|
30
|
+
@contains_virus = contains_virus
|
31
|
+
@exception = exception
|
32
|
+
@error_message = error_message
|
33
|
+
@virus_info = virus_info
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class StatsResult
|
38
|
+
attr_reader :total_pools, :state, :threads_live, :threads_idle, :threads_max
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "socket"
|
4
|
+
|
5
|
+
module RubyClamdscan
|
6
|
+
# Socket related methods to open communication
|
7
|
+
module Socket
|
8
|
+
# Open a socket to ClamAV based on RubyClamdscan.Configuration
|
9
|
+
# @param configuration [RubyClamdscan::Configuration] configuration used to determine socket
|
10
|
+
# @return [IO]
|
11
|
+
def self.open_clamav_socket(configuration)
|
12
|
+
if configuration.use_tcp_socket
|
13
|
+
open_tcp_socket(configuration)
|
14
|
+
else
|
15
|
+
open_unix_socket(configuration)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.open_tcp_socket(configuration)
|
20
|
+
TCPSocket.open(configuration.tcp_host, configuration.tcp_port)
|
21
|
+
end
|
22
|
+
|
23
|
+
private_class_method :open_tcp_socket
|
24
|
+
|
25
|
+
def self.open_unix_socket(configuration)
|
26
|
+
UNIXSocket.new(configuration.unix_socket)
|
27
|
+
end
|
28
|
+
|
29
|
+
private_class_method :open_unix_socket
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ruby_clamdscan/socket"
|
4
|
+
require "ruby_clamdscan/commands/scan"
|
5
|
+
require "ruby_clamdscan/commands/manage"
|
6
|
+
require "ruby_clamdscan/commands/status"
|
7
|
+
require "ruby_clamdscan/configuration"
|
8
|
+
require "ruby_clamdscan/version"
|
9
|
+
|
10
|
+
# Utility for interacting with ClamAV
|
11
|
+
module RubyClamdscan
|
12
|
+
class << self
|
13
|
+
# Configuration to use interacting with the ClamAV server
|
14
|
+
def configuration
|
15
|
+
@configuration ||= Configuration.new
|
16
|
+
@configuration.use_tcp_socket = true
|
17
|
+
@configuration.tcp_host = "localhost"
|
18
|
+
@configuration.tcp_port = 3310
|
19
|
+
|
20
|
+
@configuration
|
21
|
+
end
|
22
|
+
|
23
|
+
# Configure RubyClamdscan
|
24
|
+
def configure
|
25
|
+
yield(configuration)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Scans a file
|
29
|
+
# @param filepath [String] Path to file in local storage
|
30
|
+
# @return [RubyClamdscan::Models::ScanResult] Result from the scan attempt
|
31
|
+
def scan_file_from_path(filepath)
|
32
|
+
RubyClamdscan::Commands::Scan.scan_file(filepath, @configuration)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Scans the contents of the stream passed in
|
36
|
+
# @param stream [IO] stream of file contents
|
37
|
+
# @return [RubyClamdscan::Models::ScanResult] Result from the scan attempt
|
38
|
+
def scan_contents(stream)
|
39
|
+
RubyClamdscan::Commands::Scan.scan(stream, @configuration)
|
40
|
+
end
|
41
|
+
|
42
|
+
def ping_server
|
43
|
+
RubyClamdscan::Commands::Status.ping_server(@configuration)
|
44
|
+
end
|
45
|
+
|
46
|
+
def server_version
|
47
|
+
RubyClamdscan::Commands::Status.server_version(@configuration)
|
48
|
+
end
|
49
|
+
|
50
|
+
def server_stats
|
51
|
+
RubyClamdscan::Commands::Status.server_stats(@configuration)
|
52
|
+
end
|
53
|
+
|
54
|
+
def reload_server_database
|
55
|
+
RubyClamdscan::Commands::Manage.reload_server_database(@configuration)
|
56
|
+
end
|
57
|
+
|
58
|
+
def shutdown_server
|
59
|
+
RubyClamdscan::Commands::Manage.shutdown_server(@configuration)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|