"""networked spam-signature detection client"""
import os
import os.path
import socket
import signal
import cStringIO
import getopt
import tempfile
import mimetools
import sha
import pyzor
from pyzor import *
__author__ = pyzor.__author__
__version__ = pyzor.__version__
__revision__ = "$Id: client.py,v 1.41 2002/09/08 20:37:15 ftobin Exp $"
randfile = '/dev/random'
class Client(object):
__slots__ = ['socket', 'output', 'accounts']
ttl = 4
timeout = 5
max_packet_size = 8192
def __init__(self, accounts):
signal.signal(signal.SIGALRM, handle_timeout)
self.accounts = accounts
self.output = Output()
self.build_socket()
def ping(self, address):
msg = PingRequest()
self.send(msg, address)
return self.read_response(msg.get_thread())
def info(self, digest, address):
msg = InfoRequest(digest)
self.send(msg, address)
return self.read_response(msg.get_thread())
def report(self, digest, spec, address):
msg = ReportRequest(digest, spec)
self.send(msg, address)
return self.read_response(msg.get_thread())
def whitelist(self, digest, spec, address):
msg = WhitelistRequest(digest, spec)
self.send(msg, address)
return self.read_response(msg.get_thread())
def check(self, digest, address):
msg = CheckRequest(digest)
self.send(msg, address)
return self.read_response(msg.get_thread())
def shutdown(self, address):
msg = ShutdownRequest()
self.send(msg, address)
return self.read_response(msg.get_thread())
def build_socket(self):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def send(self, msg, address):
msg.init_for_sending()
account = self.accounts[address]
mac_msg_str = str(MacEnvelope.wrap(account.username,
account.keystuff.key,
msg))
self.output.debug("sending: %s" % repr(mac_msg_str))
self.socket.sendto(mac_msg_str, 0, address)
def recv(self):
return self.time_call(self.socket.recvfrom,
(self.max_packet_size,))
def time_call(self, call, varargs=(), kwargs=None):
if kwargs is None: kwargs = {}
signal.alarm(self.timeout)
try:
return apply(call, varargs, kwargs)
finally:
signal.alarm(0)
def read_response(self, expect_id):
(packet, address) = self.recv()
self.output.debug("received: %s" % repr(packet))
msg = Response(cStringIO.StringIO(packet))
msg.ensure_complete()
try:
thread_id = msg.get_thread()
if thread_id != expect_id:
if thread_id.in_ok_range():
raise ProtocolError, \
"received unexpected thread id %d (expected %d)" \
% (thread_id, expect_id)
else:
self.output.warn("received error thread id %d (expected %d)"
% (thread_id, expect_id))
except KeyError:
self.output.warn("no thread id received")
return msg
class ServerList(list):
inform_url = 'http://pyzor.sourceforge.net/cgi-bin/inform-servers-0-3-x'
def read(self, serverfile):
for line in serverfile:
orig_line = line
line = line.strip()
if line and not line.startswith('#'):
self.append(pyzor.Address.from_str(line))
class ExecCall(object):
__slots__ = ['client', 'servers', 'output']
# hard-coded for the moment
digest_spec = DataDigestSpec([(20, 3), (60, 3)])
def run(self):
debug = 0
(options, args) = getopt.getopt(sys.argv[1:], 'dh:', ['homedir='])
if len(args) < 1:
self.usage()
specified_homedir = None
for (o, v) in options:
if o == '-d':
debug = 1
elif o == '-h':
self.usage()
elif o == '--homedir':
specified_homedir = v
self.output = Output(debug=debug)
homedir = pyzor.get_homedir(specified_homedir)
config = pyzor.Config(homedir)
config.add_section('client')
defaults = {'ServersFile': 'servers',
'DiscoverServersURL': ServerList.inform_url,
'AccountsFile' : 'accounts',
}
for k, v in defaults.items():
config.set('client', k, v)
config.read(os.path.join(homedir, 'config'))
servers_fn = config.get_filename('client', 'ServersFile')
if not os.path.exists(homedir):
os.mkdir(homedir)
command = args[0]
if not os.path.exists(servers_fn) or command == 'discover':
sys.stderr.write("downloading servers from %s\n"
% config.get('client', 'DiscoverServersURL'))
download(config.get('client', 'DiscoverServersURL'), servers_fn)
self.servers = self.get_servers(servers_fn)
self.client = Client(self.get_accounts(config.get_filename('client',
'AccountsFile')))
if not self.dispatches.has_key(command):
self.usage()
dispatch = self.dispatches[command]
if dispatch is not None:
try:
if not apply(dispatch, (self, args)):
sys.exit(1)
except TimeoutError:
# note that most of the methods will trap
# their own timeout error
sys.stderr.write("timeout from server\n")
sys.exit(1)
def usage(self, s=None):
if s is not None:
sys.stderr.write("%s\n" % s)
sys.stderr.write("""usage: %s [-d] [--homedir dir] command [cmd_opts]
command is one of: check, report, discover, ping, digest, predigest,
genkey, shutdown
Data is read on standard input (stdin).
"""
% sys.argv[0])
sys.exit(2)
return # just to help xemacs
def ping(self, args):
getopt.getopt(args[1:], '')
if len(args) > 1:
self.usage("%s does not take any non-option arguments" % args[0])
runner = ClientRunner(self.client.ping)
for server in self.servers:
runner.run(server, (server,))
return runner.all_ok
def shutdown(self, args):
(opts, args2) = getopt.getopt(args[1:], '')
if len(args2) > 1:
self.usage("%s does not take any non-option arguments" % args[0])
runner = ClientRunner(self.client.shutdown)
for arg in args2:
server = Address.from_str(arg)
runner.run(server, (server,))
return runner.all_ok
def info(self, args):
getopt.getopt(args[1:], '')
if len(args) > 1:
self.usage("%s does not take any non-option arguments" % args[0])
runner = InfoClientRunner(self.client.info)
for digest in FileDigester(sys.stdin, self.digest_spec):
for server in self.servers:
response = runner.run(server, (digest, server))
return True
def check(self, args):
getopt.getopt(args[1:], '')
if len(args) > 1:
self.usage("%s does not take any non-option arguments" % args[0])
runner = CheckClientRunner(self.client.check)
for digest in FileDigester(sys.stdin, self.digest_spec):
for server in self.servers:
response = runner.run(server, (digest, server))
return (runner.found_hit and not runner.whitelisted)
def report(self, args):