1 """networked spam-signature detection server"""
3 from __future__
import division
17 __author__ = pyzor.__author__
18 __version__ = pyzor.__version__
19 __revision__ =
"$Id: server.py,v 1.29 2002-10-09 00:45:45 ftobin Exp $"
23 """signature was valid, but not permitted to
24 do the requested action"""
29 __slots__ = [
'entries']
37 self.entries.append(entry)
44 if entry.allows(user, op):
46 if entry.denies(user, op):
52 all_keyword =
'all'.lower()
58 assert bool(allow) == allow
70 allow = property(allow)
73 return self.
_says(user, op,
True)
76 return self.
_says(user, op,
False)
78 def _says(self, user, op, allow):
79 """If allow is True, we return true if and only if we allow user to do op.
80 If allow is False, we return true if and only if we deny user to do op
84 assert bool(allow) == allow
86 return (self.
allow == allow
87 and (self.
user == user
97 __slots__ = [
'file',
'output',
'lineno']
98 allow_keyword =
'allow'
109 for orig_line
in self.
file:
112 line = orig_line.strip()
113 if not line
or line.startswith(
'#'):
116 parts = line.split(
':')
119 self.output.warn(
"access file: invalid number of parts in line %d"
123 (ops_str, users_str, allow_str) = parts
126 for op_str
in ops_str.split():
129 except ValueError, e:
130 self.output.warn(
"access file: invalid opname %s line %d: %s"
131 % (repr(op_str), self.
lineno, e))
136 for u
in users_str.split():
139 except ValueError, e:
140 self.output.warn(
"access file: invalid username %s line %d: %s"
141 % (repr(u), self.
lineno, e))
145 allow_str = allow_str.strip()
151 self.output.warn(
"access file: invalid allow/deny keyword %s line %d"
152 % (repr(allow_str), self.
lineno))
157 acl.add_entry(
ACLEntry((user, op, allow)))
170 """Iteration gives (Username, long) objects
175 __slots__ = [
'file',
'output',
'lineno']
185 orig_line = self.file.readline()
191 line = orig_line.strip()
192 if not line
or line.startswith(
'#'):
194 fields = line.split(
':')
195 fields = map(
lambda x: x.strip(), fields)
198 self.output.warn(
"passwd line %d is invalid (wrong number of parts)"
203 return (
Username(fields[0]), long(fields[1], 16))
204 except ValueError, e:
205 self.output.warn(
"invalid passwd entry line %d: %s"
216 def log(self, address, user=None, command=None, arg=None, code=None):
219 if user
is None: user =
''
220 if command
is None: command =
''
221 if arg
is None: arg =
''
222 if code
is None: code = -1
226 ts = int(time.time())
227 if self.
fp is not None:
228 self.fp.write(
"%s\n" %
229 ','.join(((
"%d" % ts),
242 """Prefix conventions used in this class:
247 __slots__ = [
'r_count',
'r_entered',
'r_updated',
248 'wl_count',
'wl_entered',
'wl_updated',
250 fields = (
'r_count',
'r_entered',
'r_updated',
251 'wl_count',
'wl_entered',
'wl_updated',
291 return "%s,%d,%d,%d,%d,%d,%d" \
293 + tuple(map(
lambda x: getattr(self, x), self.
fields)))
307 raise StandardError, (
"don't know how to handle db value %s"
310 return apply(dispatch, (s,))
312 from_str = classmethod(from_str)
319 fields = (
'r_count',
'r_entered',
'r_updated')
320 assert len(parts) == len(fields)
322 for i
in range(len(parts)):
323 setattr(r, fields[i], int(parts[i]))
327 from_str_0 = classmethod(from_str_0)
332 parts = s.split(
',')[1:]
334 assert len(parts) == len(self.
fields)
336 for i
in range(len(parts)):
337 setattr(r, self.
fields[i], int(parts[i]))
341 from_str_1 = classmethod(from_str_1)
346 __slots__ = [
'output',
'initialized']
347 db_lock = threading.Lock()
348 max_age = 3600*24*30*4
351 reorganize_period = 3600*24
354 assert self.
db is not None,
"database was not initialized"
358 self.
db = gdbm.open(fn, mode)
361 initialize = classmethod(initialize)
367 self.output.debug(
"acquiring lock")
368 self.db_lock.acquire()
369 self.output.debug(
"acquired lock")
371 result = apply(method, varargs, kwargs)
373 self.output.debug(
"releasing lock")
374 self.db_lock.release()
375 self.output.debug(
"released lock")
377 apply_locking_method = classmethod(apply_locking_method)
382 def _really_getitem(self, key):
388 def _really_setitem(self, key, value):
395 self.sync_timer.start()
396 start_syncing = classmethod(start_syncing)
398 def _really_sync(self):
400 _really_sync = classmethod(_really_sync)
406 self.reorganize_timer.start()
407 start_reorganizing = classmethod(start_reorganizing)
409 def _really_reorganize(self):
410 self.output.debug(
"reorganizing the database")
411 key = self.db.firstkey()
412 breakpoint = time.time() - self.
max_age
414 while key
is not None:
415 rec = Record.from_str(self.
db[key])
417 if rec.r_updated < breakpoint:
418 self.output.debug(
"deleting key %s" % key)
420 key = self.db.nextkey(key)
424 _really_reorganize = classmethod(_really_reorganize)
427 class Server(SocketServer.ThreadingUDPServer, object):
428 max_packet_size = 8192
429 time_diff_allowance = 180
434 RequestHandler.output = self.
output
435 RequestHandler.log = log
437 self.output.debug(
'listening on %s' % str(address))
438 super(Server, self).
__init__(address, RequestHandler)
446 RequestHandler.log = newlog
447 self.output.debug(
"changing logfile")
452 super(RequestHandler, self).
setup()
472 except UnsupportedVersionError, e:
474 except NotImplementedError, e:
476 except (ProtocolError, KeyError), e:
480 except AuthorizationError, e:
482 except SignatureError, e:
483 self.
handle_error(401,
"Unauthorized, Signature Error: %s" % e)
486 traceback.print_exc()
488 self.out_msg.setdefault(
'Code', str(self.out_msg.ok_code))
489 self.out_msg.setdefault(
'Diag',
'OK')
490 self.out_msg.init_for_sending()
496 self.output.debug(
"sending: %s" % repr(msg_str))
497 self.wfile.write(msg_str)
500 def _really_handle(self):
501 """handle() without the exception handling"""
503 self.output.debug(
"received: %s" % repr(self.packet))
509 if self.
user != pyzor.anonymous_user:
510 if self.server.passwd.has_key(self.
user):
511 signed_msg.verify_sig(self.server.passwd[self.
user])
513 raise SignatureError,
"unknown user"
521 if int(self.in_msg.get_protocol_version()) != int(proto_version):
522 raise UnsupportedVersionError
526 self.
op =
Opname(self.in_msg.get_op())
527 if not self.server.acl.allows(self.
user, self.
op):
528 raise AuthorizationError,
"user is unauthorized to request the operation"
530 self.output.debug(
"got a %s command from %s" %
534 if not self.dispatches.has_key(self.
op):
535 raise NotImplementedError,
"requested operation is not implemented"
538 if dispatch
is not None:
539 apply(dispatch, (self,))
546 self.out_msg.set_thread(
ThreadId(0))
552 digest = self.
in_msg[
'Op-Digest']
554 self.output.debug(
"request to check digest %s" % digest)
558 rec = Record.from_str(db[digest])
559 r_count = rec.r_count
560 wl_count = rec.wl_count
565 self.
out_msg[
'Count'] =
"%d" % r_count
566 self.
out_msg[
'WL-Count'] =
"%d" % wl_count
570 digest = self.
in_msg[
'Op-Digest']
572 self.output.debug(
"request to report digest %s" % digest)
576 rec = Record.from_str(db[digest])
580 db[digest] = str(rec)
584 digest = self.
in_msg[
'Op-Digest']
586 self.output.debug(
"request to whitelist digest %s" % digest)
590 rec = Record.from_str(db[digest])
594 db[digest] = str(rec)
598 digest = self.
in_msg[
'Op-Digest']
600 self.output.debug(
"request to check digest %s" % digest)
604 record = Record.from_str(db[digest])
608 r_count = record.r_count
609 wl_count = record.wl_count
611 self.
out_msg[
'Entered'] =
"%d" % record.r_entered
612 self.
out_msg[
'Updated'] =
"%d" % record.r_updated
614 self.
out_msg[
'WL-Entered'] =
"%d" % record.wl_entered
615 self.
out_msg[
'WL-Updated'] =
"%d" % record.wl_updated
617 self.
out_msg[
'Count'] =
"%d" % r_count
618 self.
out_msg[
'WL-Count'] =
"%d" % wl_count
621 dispatches = {
'check': handle_check,
622 'report': handle_report,
625 'whitelist': handle_whitelist,