-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
FIX: Scan files when a single server is online (#42)
* FIX: Scan files when a single server is online This commits makes sure that even when a single server is online, the plugin is available. This commit also makes sure that a random and online server is chosen when using with_session without any specified socket. It used to use the first one. * FIX: Ensure used sockets are not closed * FIX: Add a 3 second timeout * Fix lint * Refactor ClamAV service pool * Redistribute responsibilities. This change builds upon Bianca's idea of create the `ClamAVService` class and reorganized the code based on the following rationale: `ClamAV` is what glues everything together. It knows how to talk to the S3 store, the plugin store and the `ClamAVService` to display the version, check availability, and scan files. It gets the raw response from ClamAV and translates to a different format which the rest of the plugin relies on. `ClamAVService` defines an interface of which operations are possible (`online?`, `scan_file`, `version`) and takes care of the socket and session managemente, as well as writting/reading from the socket. `ClamAVServicePool` resolves the SRV record and instantiates existing services. It tells them how to open a connection. While working on this, I tried to remove the latter and do everything using only the first two, but I decided to keep it in the end because this design is handy for testing. We rely on the `FakeTCPSocket` for spying on the communication and returning specific responses, and this design let us use without relying on a mocking framework, just dependency injection. --------- Co-authored-by: Roman Rizzi <[email protected]>
- Loading branch information
1 parent
50177ed
commit c4f3e33
Showing
10 changed files
with
211 additions
and
198 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
# frozen_string_literal: true | ||
|
||
module DiscourseAntivirus | ||
class ClamAVService | ||
def initialize(connection_factory, hostname, port) | ||
@connection_factory = connection_factory | ||
@hostname = hostname | ||
@port = port | ||
end | ||
|
||
def version | ||
with_session { |s| write_in_socket(s, "zVERSION\0") } | ||
end | ||
|
||
def online? | ||
ping_result = with_session { |s| write_in_socket(s, "zPING\0") } | ||
|
||
ping_result == "PONG" | ||
end | ||
|
||
def scan_file(file) | ||
with_session do |socket| | ||
write_in_socket(socket, "zINSTREAM\0") | ||
|
||
while data = file.read(2048) | ||
write_in_socket(socket, [data.length].pack("N")) | ||
write_in_socket(socket, data) | ||
end | ||
|
||
write_in_socket(socket, [0].pack("N")) | ||
write_in_socket(socket, "") | ||
end | ||
end | ||
|
||
private | ||
|
||
attr_reader :connection_factory, :hostname, :port, :connection | ||
|
||
def with_session | ||
socket = connection_factory.call(hostname, port) | ||
return if socket.nil? | ||
|
||
write_in_socket(socket, "zIDSESSION\0") | ||
|
||
yield(socket) | ||
|
||
write_in_socket(socket, "zEND\0") | ||
|
||
response = get_full_response_from(socket) | ||
socket.close | ||
response | ||
end | ||
|
||
# ClamAV wants us to read/write in a non-blocking manner to prevent deadlocks. | ||
# Read more about this [here](https://manpages.debian.org/testing/clamav-daemon/clamd.8.en.html#IDSESSION,) | ||
# | ||
# We need to peek into the socket buffer to make sure we can write/read from it, | ||
# or we risk ClamAV abruptly closing the connection. | ||
# For that, we use [IO#select](https://www.rubydoc.info/stdlib/core/IO.select) | ||
def write_in_socket(socket, msg) | ||
IO.select(nil, [socket]) | ||
socket.sendmsg_nonblock(msg, 0, nil) | ||
end | ||
|
||
def get_full_response_from(socket) | ||
buffer = +"" | ||
|
||
until buffer.ends_with?("\0") | ||
IO.select([socket]) | ||
|
||
# Returns an array with the chunk as the first element | ||
buffer << socket.recvmsg_nonblock(25).to_a.first.to_s | ||
end | ||
|
||
buffer.gsub("1: ", "").strip | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.