ruby_clamdscan 0.1.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/.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
|