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
-
13
1
import socket
14
2
import sys
15
3
import struct
16
4
import contextlib
17
5
import re
18
6
import base64
19
7
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\n LUZJTEUhJEgrSCo=\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
42
9
43
- class ClamdNetworkSocket ( object ) :
10
+ class ClamAVDaemon :
44
11
"""
45
12
Class for using clamd with a network socket
46
13
"""
47
- def __init__ (self , host = '127.0.0.1' , port = 3310 , timeout = None ):
48
- """
49
- class initialisation
50
14
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
54
22
"""
55
23
56
24
self .host = host
57
25
self .port = port
26
+ self .unix_socket = unix_socket
58
27
self .timeout = timeout
59
28
29
+ if self .unix_socket :
30
+ self .socket_type = socket .AF_UNIX
31
+ else :
32
+ self .socket_type = socket .AF_INET
33
+
60
34
def _init_socket (self ):
61
- """
62
- internal use only
63
- """
35
+
64
36
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
+
67
44
self .clamd_socket .settimeout (self .timeout )
68
45
69
46
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 )
89
52
90
53
def ping (self ):
91
54
return self ._basic_command ("PING" )
@@ -107,19 +70,19 @@ def shutdown(self):
107
70
"""
108
71
try :
109
72
self ._init_socket ()
110
- self ._send_command (' SHUTDOWN' )
73
+ self ._send_command (" SHUTDOWN" )
111
74
# result = self._recv_response()
112
75
finally :
113
76
self ._close_socket ()
114
77
115
78
def scan (self , file ):
116
- return self ._file_system_scan (' SCAN' , file )
79
+ return self ._file_system_scan (" SCAN" , file )
117
80
118
81
def contscan (self , file ):
119
- return self ._file_system_scan (' CONTSCAN' , file )
82
+ return self ._file_system_scan (" CONTSCAN" , file )
120
83
121
84
def multiscan (self , file ):
122
- return self ._file_system_scan (' MULTISCAN' , file )
85
+ return self ._file_system_scan (" MULTISCAN" , file )
123
86
124
87
def _basic_command (self , command ):
125
88
"""
@@ -130,7 +93,7 @@ def _basic_command(self, command):
130
93
self ._send_command (command )
131
94
response = self ._recv_response ().rsplit ("ERROR" , 1 )
132
95
if len (response ) > 1 :
133
- raise ResponseError (response [0 ])
96
+ raise exceptions . ResponseError (response [0 ])
134
97
else :
135
98
return response [0 ]
136
99
finally :
@@ -156,7 +119,7 @@ def _file_system_scan(self, command, file):
156
119
self ._send_command (command , file )
157
120
158
121
dr = {}
159
- for result in self ._recv_response_multiline ().split (' \n ' ):
122
+ for result in self ._recv_response_multiline ().split (" \n " ):
160
123
if result :
161
124
filename , reason , status = self ._parse_response (result )
162
125
dr [filename ] = (status , reason )
@@ -182,23 +145,23 @@ def instream(self, buff):
182
145
183
146
try :
184
147
self ._init_socket ()
185
- self ._send_command (' INSTREAM' )
148
+ self ._send_command (" INSTREAM" )
186
149
187
150
max_chunk_size = 1024 # MUST be < StreamMaxLength in /etc/clamav/clamd.conf
188
151
189
152
chunk = buff .read (max_chunk_size )
190
153
while chunk :
191
- size = struct .pack (b'!L' , len (chunk ))
154
+ size = struct .pack (b"!L" , len (chunk ))
192
155
self .clamd_socket .send (size + chunk )
193
156
chunk = buff .read (max_chunk_size )
194
157
195
- self .clamd_socket .send (struct .pack (b'!L' , 0 ))
158
+ self .clamd_socket .send (struct .pack (b"!L" , 0 ))
196
159
197
160
result = self ._recv_response ()
198
161
199
162
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 )
202
165
203
166
filename , reason , status = self ._parse_response (result )
204
167
return {filename : (status , reason )}
@@ -216,7 +179,7 @@ def stats(self):
216
179
"""
217
180
self ._init_socket ()
218
181
try :
219
- self ._send_command (' STATS' )
182
+ self ._send_command (" STATS" )
220
183
return self ._recv_response_multiline ()
221
184
finally :
222
185
self ._close_socket ()
@@ -226,34 +189,37 @@ def _send_command(self, cmd, *args):
226
189
`man clamd` recommends to prefix commands with z, but we will use \n
227
190
terminated strings, as python<->clamd has some problems with \0 x00
228
191
"""
229
- concat_args = ''
192
+ concat_args = ""
230
193
if args :
231
- concat_args = ' ' + ' ' .join (args )
194
+ concat_args = " " + " " .join (args )
232
195
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" )
234
198
self .clamd_socket .send (cmd )
235
199
236
200
def _recv_response (self ):
237
201
"""
238
202
receive line from clamd
239
203
"""
240
204
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 ()
243
207
except (socket .error , socket .timeout ):
244
208
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 } " )
246
210
247
211
def _recv_response_multiline (self ):
248
212
"""
249
213
receive multiple line response from clamd and strip all whitespace characters
250
214
"""
251
215
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" )
254
218
except (socket .error , socket .timeout ):
255
219
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
+ )
257
223
258
224
def _close_socket (self ):
259
225
"""
@@ -266,50 +232,12 @@ def _parse_response(self, msg):
266
232
"""
267
233
parses responses for SCAN, CONTSCAN, MULTISCAN and STREAM commands.
268
234
"""
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
- """
286
235
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
+ )
289
239
290
- def _init_socket (self ):
291
- """
292
- internal use only
293
- """
294
240
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