Skip to content
This repository was archived by the owner on Sep 19, 2023. It is now read-only.

Commit adb4ec9

Browse files
committed
Merge branch 'development'
2 parents 0047b7f + b2ff97a commit adb4ec9

File tree

7 files changed

+216
-225
lines changed

7 files changed

+216
-225
lines changed

.github/workflows/main.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Lint & test
2+
3+
on: push
4+
5+
jobs:
6+
run-linters:
7+
name: Run linters
8+
runs-on: ubuntu-latest
9+
steps:
10+
- name: Checkout code
11+
uses: actions/checkout@v2
12+
- name: Install
13+
uses: abatilo/[email protected]
14+
with:
15+
python_version: 3.7.0
16+
poetry_version: 0.12.17
17+
working_directory: .
18+
args: install
19+
- name: Run flake8
20+
uses: abatilo/[email protected]
21+
with:
22+
python_version: 3.7.0
23+
poetry_version: 0.12.17
24+
working_directory: .
25+
args: run flake8 ./clammy
26+
pytest:
27+
name: pytest
28+
runs-on: ubuntu-latest
29+
steps:
30+
- uses: actions/checkout@master
31+
- name: Install
32+
uses: abatilo/[email protected]
33+
with:
34+
python_version: 3.8.0
35+
poetry_version: 0.12.17
36+
working_directory: .
37+
args: install
38+
- name: Run pytest
39+
uses: abatilo/[email protected]
40+
with:
41+
python_version: 3.8.0
42+
poetry_version: 0.12.17
43+
working_directory: .
44+
args: run python -m pytest

.github/workflows/release.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
on:
2+
push:
3+
# Sequence of patterns matched against refs/tags
4+
tags:
5+
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
6+
7+
name: Create release
8+
9+
jobs:
10+
build:
11+
name: Create Release
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v2
16+
- name: Install
17+
uses: abatilo/[email protected]
18+
with:
19+
python_version: 3.7.0
20+
poetry_version: 0.12.17
21+
working_directory: .
22+
args: install
23+
- name: Run pytest
24+
uses: abatilo/[email protected]
25+
with:
26+
python_version: 3.7.0
27+
poetry_version: 0.12.17
28+
working_directory: .
29+
args: run python -m pytest
30+
- name: Create release
31+
id: create_release
32+
uses: actions/create-release@v1
33+
env:
34+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35+
with:
36+
tag_name: ${{ github.ref }}
37+
release_name: Release ${{ github.ref }}
38+
draft: false
39+
prerelease: false

clammy/__init__.py

Lines changed: 58 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,54 @@
1-
#!/usr/bin/env python
2-
# -*- coding: utf-8 -*-
3-
from __future__ import unicode_literals
4-
5-
try:
6-
__version__ = __import__('pkg_resources').get_distribution('clamd').version
7-
except:
8-
__version__ = ''
9-
10-
# $Source$
11-
12-
131
import socket
142
import sys
153
import struct
164
import contextlib
175
import re
186
import base64
197

20-
scan_response = re.compile(r"^(?P<path>.*): ((?P<virus>.+) )?(?P<status>(FOUND|OK|ERROR))$")
21-
EICAR = base64.b64decode(
22-
b'WDVPIVAlQEFQWzRcUFpYNTQoUF4pN0NDKTd9JEVJQ0FSLVNUQU5E'
23-
b'QVJELUFOVElWSVJVUy1URVNU\nLUZJTEUhJEgrSCo=\n'
24-
)
25-
26-
27-
class ClamdError(Exception):
28-
pass
29-
30-
31-
class ResponseError(ClamdError):
32-
pass
33-
34-
35-
class BufferTooLongError(ResponseError):
36-
"""Class for errors with clamd using INSTREAM with a buffer lenght > StreamMaxLength in /etc/clamav/clamd.conf"""
37-
38-
39-
class ConnectionError(ClamdError):
40-
"""Class for errors communication with clamd"""
41-
8+
from clammy import exceptions
429

43-
class ClamdNetworkSocket(object):
10+
class ClamAVDaemon:
4411
"""
4512
Class for using clamd with a network socket
4613
"""
47-
def __init__(self, host='127.0.0.1', port=3310, timeout=None):
48-
"""
49-
class initialisation
5014

51-
host (string) : hostname or ip address
52-
port (int) : TCP port
53-
timeout (float or None) : socket timeout
15+
def __init__(self, host="127.0.0.1", port=3310, unix_socket=None, timeout=None):
16+
"""
17+
Args:
18+
host (string): The hostname or IP address (if connecting to a network socket)
19+
port (int): TCP port (if connecting to a network socket)
20+
unix_socket (str):
21+
timeout (float or None) : socket timeout
5422
"""
5523

5624
self.host = host
5725
self.port = port
26+
self.unix_socket = unix_socket
5827
self.timeout = timeout
5928

29+
if self.unix_socket:
30+
self.socket_type = socket.AF_UNIX
31+
else:
32+
self.socket_type = socket.AF_INET
33+
6034
def _init_socket(self):
61-
"""
62-
internal use only
63-
"""
35+
6436
try:
65-
self.clamd_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
66-
self.clamd_socket.connect((self.host, self.port))
37+
self.clamd_socket = socket.socket(self.socket_type, socket.SOCK_STREAM)
38+
39+
if self.socket_type == socket.AF_INET:
40+
self.clamd_socket.connect((self.host, self.port))
41+
elif self.socket_type == socket.AF_UNIX:
42+
self.clamd_socket.connect(self.unix_socket)
43+
6744
self.clamd_socket.settimeout(self.timeout)
6845

6946
except socket.error:
70-
e = sys.exc_info()[1]
71-
raise ConnectionError(self._error_message(e))
72-
73-
def _error_message(self, exception):
74-
# args for socket.error can either be (errno, "message")
75-
# or just "message"
76-
if len(exception.args) == 1:
77-
return "Error connecting to {host}:{port}. {msg}.".format(
78-
host=self.host,
79-
port=self.port,
80-
msg=exception.args[0]
81-
)
82-
else:
83-
return "Error {erno} connecting {host}:{port}. {msg}.".format(
84-
erno=exception.args[0],
85-
host=self.host,
86-
port=self.port,
87-
msg=exception.args[1]
88-
)
47+
if self.socket_type == socket.AF_UNIX:
48+
error_message = f'Error connecting to Unix socket "{self.unix_socket}"'
49+
elif self.socket_Type == socket.AF_INET:
50+
error_message = f'Error connecting to network socket with host "{self.host}" and port "{self.port}"'
51+
raise exceptions.ConnectionError(error_message)
8952

9053
def ping(self):
9154
return self._basic_command("PING")
@@ -107,19 +70,19 @@ def shutdown(self):
10770
"""
10871
try:
10972
self._init_socket()
110-
self._send_command('SHUTDOWN')
73+
self._send_command("SHUTDOWN")
11174
# result = self._recv_response()
11275
finally:
11376
self._close_socket()
11477

11578
def scan(self, file):
116-
return self._file_system_scan('SCAN', file)
79+
return self._file_system_scan("SCAN", file)
11780

11881
def contscan(self, file):
119-
return self._file_system_scan('CONTSCAN', file)
82+
return self._file_system_scan("CONTSCAN", file)
12083

12184
def multiscan(self, file):
122-
return self._file_system_scan('MULTISCAN', file)
85+
return self._file_system_scan("MULTISCAN", file)
12386

12487
def _basic_command(self, command):
12588
"""
@@ -130,7 +93,7 @@ def _basic_command(self, command):
13093
self._send_command(command)
13194
response = self._recv_response().rsplit("ERROR", 1)
13295
if len(response) > 1:
133-
raise ResponseError(response[0])
96+
raise exceptions.ResponseError(response[0])
13497
else:
13598
return response[0]
13699
finally:
@@ -156,7 +119,7 @@ def _file_system_scan(self, command, file):
156119
self._send_command(command, file)
157120

158121
dr = {}
159-
for result in self._recv_response_multiline().split('\n'):
122+
for result in self._recv_response_multiline().split("\n"):
160123
if result:
161124
filename, reason, status = self._parse_response(result)
162125
dr[filename] = (status, reason)
@@ -182,23 +145,23 @@ def instream(self, buff):
182145

183146
try:
184147
self._init_socket()
185-
self._send_command('INSTREAM')
148+
self._send_command("INSTREAM")
186149

187150
max_chunk_size = 1024 # MUST be < StreamMaxLength in /etc/clamav/clamd.conf
188151

189152
chunk = buff.read(max_chunk_size)
190153
while chunk:
191-
size = struct.pack(b'!L', len(chunk))
154+
size = struct.pack(b"!L", len(chunk))
192155
self.clamd_socket.send(size + chunk)
193156
chunk = buff.read(max_chunk_size)
194157

195-
self.clamd_socket.send(struct.pack(b'!L', 0))
158+
self.clamd_socket.send(struct.pack(b"!L", 0))
196159

197160
result = self._recv_response()
198161

199162
if len(result) > 0:
200-
if result == 'INSTREAM size limit exceeded. ERROR':
201-
raise BufferTooLongError(result)
163+
if result == "INSTREAM size limit exceeded. ERROR":
164+
raise exceptions.BufferTooLongError(result)
202165

203166
filename, reason, status = self._parse_response(result)
204167
return {filename: (status, reason)}
@@ -216,7 +179,7 @@ def stats(self):
216179
"""
217180
self._init_socket()
218181
try:
219-
self._send_command('STATS')
182+
self._send_command("STATS")
220183
return self._recv_response_multiline()
221184
finally:
222185
self._close_socket()
@@ -226,34 +189,37 @@ def _send_command(self, cmd, *args):
226189
`man clamd` recommends to prefix commands with z, but we will use \n
227190
terminated strings, as python<->clamd has some problems with \0x00
228191
"""
229-
concat_args = ''
192+
concat_args = ""
230193
if args:
231-
concat_args = ' ' + ' '.join(args)
194+
concat_args = " " + " ".join(args)
232195

233-
cmd = 'n{cmd}{args}\n'.format(cmd=cmd, args=concat_args).encode('utf-8')
196+
#cmd = 'n{cmd}{args}\n'.format(cmd=cmd, args=concat_args).encode('utf-8')
197+
cmd = f"n{cmd}{concat_args}\n".encode("utf-8")
234198
self.clamd_socket.send(cmd)
235199

236200
def _recv_response(self):
237201
"""
238202
receive line from clamd
239203
"""
240204
try:
241-
with contextlib.closing(self.clamd_socket.makefile('rb')) as f:
242-
return f.readline().decode('utf-8').strip()
205+
with contextlib.closing(self.clamd_socket.makefile("rb")) as f:
206+
return f.readline().decode("utf-8").strip()
243207
except (socket.error, socket.timeout):
244208
e = sys.exc_info()[1]
245-
raise ConnectionError("Error while reading from socket: {0}".format(e.args))
209+
raise ConnectionError(f"Error while reading from socket: {e.args}")
246210

247211
def _recv_response_multiline(self):
248212
"""
249213
receive multiple line response from clamd and strip all whitespace characters
250214
"""
251215
try:
252-
with contextlib.closing(self.clamd_socket.makefile('rb')) as f:
253-
return f.read().decode('utf-8')
216+
with contextlib.closing(self.clamd_socket.makefile("rb")) as f:
217+
return f.read().decode("utf-8")
254218
except (socket.error, socket.timeout):
255219
e = sys.exc_info()[1]
256-
raise ConnectionError("Error while reading from socket: {0}".format(e.args))
220+
raise exceptions.ConnectionError(
221+
f"Error while reading from socket: {e.args}"
222+
)
257223

258224
def _close_socket(self):
259225
"""
@@ -266,50 +232,12 @@ def _parse_response(self, msg):
266232
"""
267233
parses responses for SCAN, CONTSCAN, MULTISCAN and STREAM commands.
268234
"""
269-
try:
270-
return scan_response.match(msg).group("path", "virus", "status")
271-
except AttributeError:
272-
raise ResponseError(msg.rsplit("ERROR", 1)[0])
273-
274-
275-
class ClamdUnixSocket(ClamdNetworkSocket):
276-
"""
277-
Class for using clamd with an unix socket
278-
"""
279-
def __init__(self, path="/var/run/clamav/clamd.ctl", timeout=None):
280-
"""
281-
class initialisation
282-
283-
path (string) : unix socket path
284-
timeout (float or None) : socket timeout
285-
"""
286235

287-
self.unix_socket = path
288-
self.timeout = timeout
236+
scan_response = re.compile(
237+
r"^(?P<path>.*): ((?P<virus>.+) )?(?P<status>(FOUND|OK|ERROR))$"
238+
)
289239

290-
def _init_socket(self):
291-
"""
292-
internal use only
293-
"""
294240
try:
295-
self.clamd_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
296-
self.clamd_socket.connect(self.unix_socket)
297-
self.clamd_socket.settimeout(self.timeout)
298-
except socket.error:
299-
e = sys.exc_info()[1]
300-
raise ConnectionError(self._error_message(e))
301-
302-
def _error_message(self, exception):
303-
# args for socket.error can either be (errno, "message")
304-
# or just "message"
305-
if len(exception.args) == 1:
306-
return "Error connecting to {path}. {msg}.".format(
307-
path=self.unix_socket,
308-
msg=exception.args[0]
309-
)
310-
else:
311-
return "Error {erno} connecting {path}. {msg}.".format(
312-
erno=exception.args[0],
313-
path=self.unix_socket,
314-
msg=exception.args[1]
315-
)
241+
return scan_response.match(msg).group("path", "virus", "status")
242+
except AttributeError:
243+
raise exceptions.ResponseError(msg.rsplit("ERROR", 1)[0])

0 commit comments

Comments
 (0)