"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")