[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[minion-cvs] SMTP and blacklists are implemented (but not tested)
Update of /home/minion/cvsroot/src/minion/lib/mixminion/server
In directory moria.mit.edu:/tmp/cvs-serv9353/lib/mixminion/server
Modified Files:
Modules.py
Log Message:
SMTP and blacklists are implemented (but not tested)
Index: Modules.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/server/Modules.py,v
retrieving revision 1.9
retrieving revision 1.10
diff -u -d -r1.9 -r1.10
--- Modules.py 4 Jan 2003 04:38:45 -0000 1.9
+++ Modules.py 4 Jan 2003 20:42:38 -0000 1.10
@@ -20,6 +20,11 @@
import socket
import base64
+if sys.version_info[:2] >= (2,3):
+ import textwrap
+else:
+ import mixminion._textwrap as textwrap
+
import mixminion.BuildMessage
import mixminion.Config
import mixminion.Packet
@@ -106,7 +111,7 @@
DELIVER_FAIL_NORETRY (if the message shouldn't be tried later).
(This method is only used by your delivery queue; if you use
- a nonstandard delivery queue, you don't need to implement this."""
+ a nonstandard delivery queue, you don't need to implement this.)"""
raise NotImplementedError("processMessage")
class ImmediateDeliveryQueue:
@@ -211,6 +216,7 @@
self.registerModule(MBoxModule())
self.registerModule(DropModule())
+ self.registerModule(DirectSMTPModule())
self.registerModule(MixmasterSMTPModule())
self._isConfigured = 0
@@ -379,6 +385,104 @@
return DELIVER_OK
#----------------------------------------------------------------------
+class EmailAddressSet:
+ """A set of email addresses stored on disk, for use in blacklisting email
+ addresses. DOCDOC"""
+ #XXXX002 test
+ ## Fields
+ # addresses -- A dict whose keys are lowercased email addresses
+ # domains -- A dict whose keys are lowercased domains ("foo.bar.baz")
+ # DOCDOC sub
+ # users -- A dict whose keys are lowercased users ("foo")
+ # patterns -- A list of regular expression objects.
+ def __init__(self, fname=None, string=None):
+ """Read the address set from a file or a string. DOCDOC"""
+ if string is None:
+ f = open(fname, 'r')
+ string = f.read()
+ f.close()
+
+ self.addresses = {}
+ self.domains = {}
+ self.users = {}
+ self.patterns = []
+
+ lines = string.split("\n")
+ lineno = 0
+ for line in lines:
+ lineno += 1
+ line = line.strip()
+ if not line or line[0] == '#':
+ # Blank line or comment; skip.
+ continue
+ line = line.split(" ", 1)
+ if len(line) != 2:
+ raise ConfigError("Invalid line at %s"%lineno)
+ cmd = line[0].lower()
+ arg = line[1].strip()
+ if cmd == 'address':
+ if not isSMTPMailbox(arg):
+ LOG.warn("Address %s on %s doesn't look valid to me.",
+ arg, lineno)
+ self.addresses[arg.lower()] = 1
+ elif cmd == 'user':
+ if not isSMTPMailbox(arg+"@x"):
+ LOG.warn("User %s on %s doesn't look valid to me.",
+ arg, lineno)
+ self.users[arg.lower()] = 1
+ elif cmd == 'domain':
+ if not isSMTPMailbox("x@"+arg):
+ LOG.warn("Domain %s on %s doesn't look valid to me.",
+ arg, lineno)
+ if not self.domains.has_key(arg.lower()):
+ self.domains[arg.lower()] = 1
+ elif cmd == 'subdomains':
+ if not isSMTPMailbox("x@"+arg):
+ LOG.warn("Domain %s on %s doesn't look valid to me.",
+ arg, lineno)
+ self.domains[arg.lower()] = 'SUB'
+ elif cmd == 'pattern':
+ if not arg[0] == '/' and arg[-1] == '/':
+ raise ConfigError("Pattern %s on %s is missing /s.",
+ arg, lineno)
+ arg = arg[1:-1]
+ # FFFF As an optimization, we may be able to coalesce some
+ # FFFF of these patterns. I doubt this will become
+ self.patterns.append(re.compile(arg, re.I))
+ else:
+ raise ConfigError("Unrecognized command %s on line %s",
+ cmd, lineno)
+
+ def contains(self, address):
+ """Return true iff this this address set contains the address
+ 'address'.
+
+ The result will only be accurate if 'address' is a
+ valid restricted RFC822 address as checked by isSMTPMailbox.
+ """
+ #DOCDOC
+ lcaddress = address.lower()
+ if self.addresses.has_key(lcaddress):
+ return 1
+
+ user, dom = lcaddress.split("@", 1)
+ if self.users.has_key(user) or self.domains.has_key(dom):
+ return 1
+
+ domparts = dom.split(".")
+ domparts.reverse()
+ dom = domparts[0]
+ if self.domains.get(
+ for d in domparts[1:]:
+
+
+ for pat in self.patterns:
+ if pat.search(address):
+ return 1
+
+ return 0
+
+#----------------------------------------------------------------------
class MBoxModule(DeliveryModule):
"""Implementation for MBOX delivery: sends messages, via SMTP, to
addresses from a local file. The file must have the format
@@ -428,7 +532,6 @@
LOG.warn("Value of %s (%s) doesn't look like an email address",
field, sec[field])
-
def configure(self, config, moduleManager):
if not config['Delivery/MBOX'].get("Enabled", 0):
moduleManager.disableModule(self)
@@ -535,6 +638,80 @@
def getExitTypes(self):
return [ mixminion.Packet.SMTP_TYPE ]
+class DirectSMTPModule(SMTPModule):
+ #XXXX DOCDOC!!!!
+ # server
+ # blacklist
+ # server
+ # header
+ # returnAddress
+ #XXXX002 test
+ def __init__(self):
+ SMTPModule.__init__(self)
+
+ def getConfigSyntax(self):
+ return { "Delivery/SMTP" :
+ { 'Enabled' : ('REQUIRE', _parseBoolean, "no"),
+ 'BlacklistFile' : ('ALLOW', None, None),
+ 'SMTPServer' : ('ALLOW', None, 'localhost'), #Required on e
+ 'Messsage' : ('ALLOW', None, ""),
+ 'ReturnAddress': ('ALLOW', None, None), #Required on e
+ 'SubjectLine' : ('ALLOW', None,
+ 'Type-III Anonymous Message'),
+
+ }
+ }
+
+ def validateConfig(self, sections, entries, lines, contents):
+ sec = sections['Delivery/SMTP']
+ if not sec.get('Enabled'):
+ return
+ for field in 'SMTPServer', 'ReturnAddress':
+ if not sec.get(field):
+ raise ConfigError("Missing field %s in [Delivery/SMTP]"%field)
+ fn = sec.get('BlacklistFile')
+ if fn and not not os.path.exists(fn):
+ raise ConfigError("Blacklist file %s seems not to exist"%fn)
+ if not isSMTPMailbox(sec['ReturnAddress']):
+ LOG.warn("Return address (%s) doesn't look like an email address",
+ sec['ReturnAddress'])
+
+ def configure(self, config, manager):
+ sec = config['Delivery/SMTP']
+ if not sec.get('Enabled'):
+ manager.disableModule(self)
+ return
+
+ self.server = sec['SMTPServer']
+ if sec['BlacklistFile']:
+ self.blacklist = EmailAddressSet(fname=sec['BlacklistFile'])
+ else:
+ self.blacklist = None
+ message = textwrap.wrap(sec.get('Message',"")).strip()
+ subject = sec['Subject']
+ self.returnAddress = sec['ReturnAddress']
+
+ self.header = "From: %s\nSubject: %s\n\n%s" %(
+ self.returnAddress, subject, message)
+
+ def processMessage(self, message, tag, exitType, address):
+ assert exitType == mixminion.Packet.SMTP_TYPE
+ LOG.trace("Received SMTP message")
+ # parseSMTPInfo will raise a parse error if the mailbox is invalid.
+ address = mixminion.Packet.parseSMTPInfo(address).email
+
+ # Now, have we blacklisted this address?
+ if self.blacklist and self.blacklist.contains(address):
+ LOG.warn("Dropping message to blacklisted address %r", address)
+ return DELIVER_FAIL_NORETRY
+
+ # Decode and escape the message, and get ready to send it.
+ msg = _escapeMessageForEmail(message, tag)
+ msg = "To: %s\n%s\n\n%s"%(address, self.header, msg)
+
+ # Send the message.
+ return sendSMTPMessage(self.server, [address], self.returnAddress, msg)
+
class MixmasterSMTPModule(SMTPModule):
"""Implements SMTP by relaying messages via Mixmaster nodes. This
is kind of unreliable and kludgey, but it does allow us to
@@ -632,6 +809,9 @@
MTA."""
# FFFF This implementation can stall badly if we don't have a fast
# FFFF local MTA.
+
+ # FFFF We should leave the connection open if we're going to send many
+ # FFFF messages in a row.
LOG.trace("Sending message via SMTP host %s to %s", server, toList)
con = smtplib.SMTP(server)
try: