"Fossies" - the Fresh Open Source Software Archive 
Member "pyzor-1.0.0/pyzor/client.py" (10 Dec 2014, 10814 Bytes) of package /linux/privat/pyzor-1.0.0.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style:
standard) with prefixed line numbers.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "client.py" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
0.9.0_vs_1.0.0.
1 """Networked spam-signature detection client.
2
3 >>> import pyzor
4 >>> import pyzor.client
5 >>> import pyzor.digest
6 >>> import pyzor.config
7
8 To load the accounts file:
9
10 >>> accounts = pyzor.config.load_accounts(filename)
11
12 To create a client (to then issue commands):
13
14 >>> client = pyzor.client.Client(accounts)
15
16 To create a client, using the anonymous user:
17
18 >>> client = pyzor.client.Client()
19
20 To get a digest (of an email.message.Message object, or similar):
21
22 >>> digest = pyzor.digest.get_digest(msg)
23
24 To query a server (where address is a (host, port) pair):
25
26 >>> client.ping(address)
27 >>> client.info(digest, address)
28 >>> client.report(digest, address)
29 >>> client.whitelist(digest, address)
30 >>> client.check(digest, address)
31
32 To query the default server (public.pyzor.org):
33
34 >>> client.ping()
35 >>> client.info(digest)
36 >>> client.report(digest)
37 >>> client.whitelist(digest)
38 >>> client.check(digest)
39
40 Response will contain, depending on the type of request, some
41 of the following keys (e.g. client.ping()['Code']):
42
43 All responses will have:
44 - 'Diag' 'OK' or error message
45 - 'Code' '200' if OK
46 - 'PV' Protocol Version
47 - 'Thread'
48
49 `info` and `check` responses will also contain:
50 - '[WL-]Count' Whitelist/Blacklist count
51
52 `info` responses will also have:
53 - '[WL-]Entered' timestamp when message was first whitelisted/blacklisted
54 - '[WL-]Updated' timestamp when message was last whitelisted/blacklisted
55 """
56
57 import time
58 import email
59 import socket
60 import logging
61 import functools
62 import collections
63
64 import pyzor.digest
65 import pyzor.account
66 import pyzor.message
67
68 import pyzor.hacks.py26
69
70 pyzor.hacks.py26.hack_email()
71
72
73 class Client(object):
74 timeout = 5
75 max_packet_size = 8192
76
77 def __init__(self, accounts=None, timeout=None, spec=None):
78 if accounts is None:
79 accounts = {}
80 self.accounts = dict(((host, int(port)), account)
81 for (host, port), account in accounts.iteritems())
82 if spec is None:
83 spec = pyzor.digest.digest_spec
84 self.spec = spec
85 if timeout is not None:
86 self.timeout = timeout
87 self.log = logging.getLogger("pyzor")
88
89 def ping(self, address=("public.pyzor.org", 24441)):
90 msg = pyzor.message.PingRequest()
91 sock = self.send(msg, address)
92 return self.read_response(sock, msg.get_thread())
93
94 def pong(self, digest, address=("public.pyzor.org", 24441)):
95 msg = pyzor.message.PongRequest(digest)
96 sock = self.send(msg, address)
97 return self.read_response(sock, msg.get_thread())
98
99 def info(self, digest, address=("public.pyzor.org", 24441)):
100 msg = pyzor.message.InfoRequest(digest)
101 sock = self.send(msg, address)
102 return self.read_response(sock, msg.get_thread())
103
104 def report(self, digest, address=("public.pyzor.org", 24441)):
105 msg = pyzor.message.ReportRequest(digest, self.spec)
106 sock = self.send(msg, address)
107 return self.read_response(sock, msg.get_thread())
108
109 def whitelist(self, digest, address=("public.pyzor.org", 24441)):
110 msg = pyzor.message.WhitelistRequest(digest, self.spec)
111 sock = self.send(msg, address)
112 return self.read_response(sock, msg.get_thread())
113
114 def check(self, digest, address=("public.pyzor.org", 24441)):
115 msg = pyzor.message.CheckRequest(digest)
116 sock = self.send(msg, address)
117 return self.read_response(sock, msg.get_thread())
118
119 def _mock_check(self, digests, address=None):
120 msg = (b"Code: %s\nDiag: OK\nPV: %s\nThread: 1024\nCount: 0\n"
121 b"WL-Count: 0" % (pyzor.message.Response.ok_code,
122 pyzor.proto_version))
123 return email.message_from_bytes(msg, _class=pyzor.message.Response)
124
125 def send(self, msg, address=("public.pyzor.org", 24441)):
126 address = (address[0], int(address[1]))
127 msg.init_for_sending()
128 try:
129 account = self.accounts[address]
130 except KeyError:
131 account = pyzor.account.AnonymousAccount
132 timestamp = int(time.time())
133 msg["User"] = account.username
134 msg["Time"] = str(timestamp)
135 msg["Sig"] = pyzor.account.sign_msg(pyzor.account.hash_key(
136 account.key, account.username), timestamp, msg)
137 self.log.debug("sending: %r", msg.as_string())
138 return self._send(msg, address)
139
140 @staticmethod
141 def _send(msg, addr):
142 sock = None
143 for res in socket.getaddrinfo(addr[0], addr[1], 0, socket.SOCK_DGRAM,
144 socket.IPPROTO_UDP):
145 af, socktype, proto, _, sa = res
146 try:
147 sock = socket.socket(af, socktype, proto)
148 except socket.error:
149 sock = None
150 continue
151 try:
152 sock.sendto(msg.as_string().encode("utf8"), 0, sa)
153 except socket.timeout:
154 sock.close()
155 raise pyzor.TimeoutError("Sending to %s time-outed" % sa)
156 except socket.error:
157 sock.close()
158 sock = None
159 continue
160 break
161 if sock is None:
162 raise pyzor.CommError("Unable to send to %s:%s" % addr)
163 return sock
164
165 def read_response(self, sock, expected_id):
166 sock.settimeout(self.timeout)
167 try:
168 packet, address = sock.recvfrom(self.max_packet_size)
169 except socket.timeout as ex:
170 sock.close()
171 raise pyzor.TimeoutError("Reading response timed-out.")
172 except socket.error as ex:
173 sock.close()
174 raise pyzor.CommError("Socket error while reading response: %s" %
175 ex)
176
177 self.log.debug("received: %r/%r", packet, address)
178 msg = email.message_from_bytes(packet, _class=pyzor.message.Response)
179 msg.ensure_complete()
180 try:
181 thread_id = msg.get_thread()
182 if thread_id != expected_id:
183 if thread_id.in_ok_range():
184 raise pyzor.ProtocolError(
185 "received unexpected thread id %d (expected %d)" %
186 (thread_id, expected_id))
187 self.log.warn("received error thread id %d (expected %d)",
188 thread_id, expected_id)
189 except KeyError:
190 self.log.warn("no thread id received")
191 return msg
192
193
194 class BatchClient(Client):
195 """Like the normal Client but with support for batching reports."""
196
197 def __init__(self, accounts=None, timeout=None, spec=None, batch_size=10):
198 Client.__init__(self, accounts=accounts, timeout=timeout, spec=spec)
199 self.batch_size = batch_size
200 self.r_requests = {}
201 self.w_requests = {}
202 self.flush()
203
204 def report(self, digest, address=("public.pyzor.org", 24441)):
205 self._add_digest(digest, address, self.r_requests)
206
207 def whitelist(self, digest, address=("public.pyzor.org", 24441)):
208 self._add_digest(digest, address, self.w_requests)
209
210 def _add_digest(self, digest, address, requests):
211 address = (address[0], int(address[1]))
212 msg = requests[address]
213
214 msg.add_digest(digest)
215 if msg.digest_count >= self.batch_size:
216 try:
217 return self.send(msg, address)
218 finally:
219 del requests[address]
220
221 def flush(self):
222 """Deleting any saved digest reports."""
223 self.r_requests = collections.defaultdict(
224 functools.partial(pyzor.message.ReportRequest, spec=self.spec))
225 self.w_requests = collections.defaultdict(
226 functools.partial(pyzor.message.WhitelistRequest, spec=self.spec))
227
228 def force(self):
229 """Force send any remaining reports."""
230 for address, msg in self.r_requests.iteritems():
231 try:
232 self.send(msg, address)
233 except:
234 continue
235 for address, msg in self.w_requests.iteritems():
236 try:
237 self.send(msg, address)
238 except:
239 continue
240
241 def __del__(self):
242 self.force()
243
244
245 class ClientRunner(object):
246 def __init__(self, routine):
247 self.log = logging.getLogger("pyzor")
248 self.routine = routine
249 self.all_ok = True
250 self.results = []
251
252 def run(self, server, args, kwargs=None):
253 if kwargs is None:
254 kwargs = {}
255 message = "%s:%s\t" % server
256 response = None
257 try:
258 response = self.routine(*args, **kwargs)
259 self.handle_response(response, message)
260 except (pyzor.CommError, KeyError, ValueError) as e:
261 self.results.append("%s%s\n" % (message, (e.code, str(e))))
262 self.log.error("%s\t%s: %s", server, e.__class__.__name__, e)
263 self.all_ok = False
264
265 def handle_response(self, response, message):
266 """mesaage is a string we've built up so far"""
267 if not response.is_ok():
268 self.all_ok = False
269 self.results.append("%s%s\n" % (message, response.head_tuple()))
270
271
272 class CheckClientRunner(ClientRunner):
273 def __init__(self, routine, r_count=0, wl_count=0):
274 ClientRunner.__init__(self, routine)
275 self.found_hit = False
276 self.whitelisted = False
277 self.hit_count = 0
278 self.whitelist_count = 0
279 self.r_count_found = r_count
280 self.wl_count_clears = wl_count
281
282 def handle_response(self, response, message):
283 message += "%s\t" % str(response.head_tuple())
284 if response.is_ok():
285 self.hit_count = int(response['Count'])
286 self.whitelist_count = int(response['WL-Count'])
287 if self.whitelist_count > self.wl_count_clears:
288 self.whitelisted = True
289 elif self.hit_count > self.r_count_found:
290 self.found_hit = True
291 message += "%d\t%d" % (self.hit_count, self.whitelist_count)
292 else:
293 self.all_ok = False
294 self.results.append(message + "\n")
295
296
297 class InfoClientRunner(ClientRunner):
298 def handle_response(self, response, message):
299 message += "%s\n" % str(response.head_tuple())
300
301 if response.is_ok():
302 for key in ('Count', 'Entered', 'Updated', 'WL-Count',
303 'WL-Entered', 'WL-Updated'):
304 if key in response:
305 val = int(response[key])
306 if 'Count' in key:
307 stringed = str(val)
308 elif val == -1:
309 stringed = 'Never'
310 else:
311 stringed = time.ctime(val)
312 message += ("\t%s: %s\n" % (key, stringed))
313 else:
314 self.all_ok = False
315 self.results.append(message + "\n")