Here's a patch to spamcheck.py that allows for multiple recipient support yet
preserves per-user pgsql preferences.  It's designed to work with postfix, 
but could probably be used with almost any modern MTA.  

It works by checking if each user has any preferences set in the SA userpref 
database.  When a message with multiple recipients is recieved, recipients 
with spam preferences are processed first.  Then the mail is scanned once for 
all other recipients (using the username 'nobody') and sent on to the lmtp 
server.  

Right now, it only works for postgres using the pg python module.  (I 
couldn't find any decent documentation for pgdb)  However, it should be easy 
to port to other db schema.  

I'd like to get a modified version of this in CVS, if possible,  It still 
needs a little work in order to generalize the DB support and clean out any 
bugs I've missed.  

Thoughts?  Is this going to be useful to anyone besides us?

*** spamcheck.py_2.43	Thu Oct 24 17:03:56 2002
--- spamcheck.py_pg_2.43_clean	Thu Oct 24 17:53:11 2002
***************
*** 1,4 ****
! #!/usr/bin/env python
  
  # spamcheck.py: spam tagging support for Postfix/Cyrus
  #
--- 1,4 ----
! #!/usr/local/bin/python
  
  # spamcheck.py: spam tagging support for Postfix/Cyrus
  #
***************
*** 35,40 ****
--- 35,41 ----
  import sys, string
  import re, getopt
  import smtplib, socket
+ import pg
  
  # EX_TEMPFAIL is 75 on every Unix I've checked, but...
  # check /usr/include/sysexits.h if you have odd problems.
***************
*** 43,48 ****
--- 44,57 ----
  NOUSER = 67
  TEMPFAIL = 75
  
+ # Postgres variables
+ PG_DB = 'DBNAME'
+ PG_HOSTNAME = 'HOSTNAME.EXAMPLE.NET'
+ PG_PORT = 5432
+ PG_TABLE = 'TABLENAME'
+ PG_USERNAME = 'USERNAME'
+ PG_PASSWORD = 'PASSWORD'
+ 
  # this class hacks smtplib's SMTP class into a shape where it will
  # successfully pass a message off to Cyrus's LMTP daemon.
  # Also adds support for connecting to a unix domain socket.
***************
*** 171,200 ****
      # return the spam checked message
      return fd.read()
  
! def process_message(spamd_host, spamd_port, lmtp_host, sender, recipient):
      CHUNKSIZE = 256 * 1024
-     # read in the first chunk of the message
-     
-     data = sys.stdin.read(CHUNKSIZE)
- 
      # is the message smaller than the maximum size?
      if len(data) < CHUNKSIZE:
!         try:
!             checked_data = spamcheck(spamd_host, spamd_port, recipient, data)
!             # get rid of From ... line if present
!             if checked_data[:5] == 'From ':
!                 nl = string.find(checked_data, '\n')
!                 if nl >= 0: checked_data = checked_data[nl+1:]
!         except:
!             sys.stderr.write('%s: %s\n' % sys.exc_info()[:2])
!             checked_data = data # fallback
          lmtp = LMTP(lmtp_host)
          code, msg = lmtp.lhlo()
          if code != 250: sys.exit(TEMPFAIL)
  
          #lmtp.set_debuglevel(1)
          try:
!             lmtp.sendmail(sender, recipient, checked_data)
          except smtplib.SMTPRecipientsRefused:
              sys.exit(NOUSER)
          except smtplib.SMTPDataError, errors:
--- 180,235 ----
      # return the spam checked message
      return fd.read()
  
! def process_message(spamd_host, spamd_port, lmtp_host, sender, recipients, users, data):
      CHUNKSIZE = 256 * 1024
      # is the message smaller than the maximum size?
      if len(data) < CHUNKSIZE:
!         if len(recipients) == 1:
!             try:
!                 checked_data = spamcheck(spamd_host, spamd_port, users[0], data)
!                 # get rid of From ... line if present
!                 if checked_data[:5] == 'From ':
!                     nl = string.find(checked_data, '\n')
!                     if nl >= 0: checked_data = checked_data[nl+1:]
!             except:
!                 sys.stderr.write('%s: %s\n' % sys.exc_info()[:2])
!                 checked_data = data # fallback
!         else:  # More than one recipient
!             default_recipients = list()
!             conn = pg.connect(PG_DB, PG_HOSTNAME, PG_PORT, None, None, PG_USERNAME, PG_PASSWORD)
!             for index in range(len(recipients)):
!                 recipient = recipients[index]
!                 user = users[index]
!                 connstring = 'select * from ' + PG_TABLE + ' where username = \'' + user + '\''
!                 result = conn.query(connstring)
!                 if result.ntuples() != 0:
!                     try:
!                         process_message(spamd_host, spamd_port, lmtp_host, sender, [recipient], [user], data)
!                     except:
!                         sys.stderr.write('%s: %s\n' % sys.exc_info()[:2])
!                         sys.exit(1)
!                 else:
!                     default_recipients.append(recipient)
!             if len(default_recipients) != 0:
!                 recipients = default_recipients  #Non-default users are done
!                 try:
!                     checked_data = spamcheck(spamd_host, spamd_port, 'nobody', data)
!                     # get rid of From ... line if present
!                     if checked_data[:5] == 'From ':
!                         nl = string.find(checked_data, '\n')
!                         if nl >= 0: checked_data = checked_data[nl+1:]
!                 except:
!                     sys.stderr.write('%s: %s\n' % sys.exc_info()[:2])
!                     checked_data = data # fallback
!             else: # All recipients had custom spam preferences
!                 sys.exit(0)
          lmtp = LMTP(lmtp_host)
          code, msg = lmtp.lhlo()
          if code != 250: sys.exit(TEMPFAIL)
  
          #lmtp.set_debuglevel(1)
          try:
!             lmtp.sendmail(sender, recipients, checked_data)
          except smtplib.SMTPRecipientsRefused:
              sys.exit(NOUSER)
          except smtplib.SMTPDataError, errors:
***************
*** 211,218 ****
  
          code, msg = lmtp.mail(sender)
          if code != 250: sys.exit(TEMPFAIL)
!         code, msg = lmtp.rcpt(recipient)
!         if code != 250: sys.exit(NOUSER)
  
          # send the data in chunks
          lmtp.putcmd("data")
--- 246,254 ----
  
          code, msg = lmtp.mail(sender)
          if code != 250: sys.exit(TEMPFAIL)
!         for recipient in recipients:
!             code, msg = lmtp.rcpt(recipient)
!             if code != 250: sys.exit(NOUSER)
  
          # send the data in chunks
          lmtp.putcmd("data")
***************
*** 238,265 ****
      spamd_port = 783
      lmtp_host = None
      sender = None
!     recipient = None
      try:
!         opts, args = getopt.getopt(argv[1:], 's:r:l:')
      except getopt.error, err:
          sys.stderr.write('%s: %s\n' % (argv[0], err))
          sys.exit(USAGE)
      for opt, arg in opts:
          if opt == '-s': sender = arg
-         elif opt == '-r': recipient = string.lower(arg)
          elif opt == '-l': lmtp_host = arg
          else:
              sys.stderr.write('unexpected argument\n')
              sys.exit(USAGE)
      if args:
!         sys.stderr.write('unexpected argument\n')
!         sys.exit(USAGE)
!     if not lmtp_host or not sender or not recipient:
          sys.stderr.write('required argument missing\n')
          sys.exit(USAGE)
  
      try:
!         process_message(spamd_host, spamd_port, lmtp_host, sender, recipient)
      except SystemExit, status:
          raise SystemExit, status
      except:
--- 274,318 ----
      spamd_port = 783
      lmtp_host = None
      sender = None
!     destination = None
!     num_recipients = 0
!     recipients = None
!     users = None
      try:
!         opts, args = getopt.getopt(argv[1:], 's:l:')
      except getopt.error, err:
          sys.stderr.write('%s: %s\n' % (argv[0], err))
          sys.exit(USAGE)
      for opt, arg in opts:
          if opt == '-s': sender = arg
          elif opt == '-l': lmtp_host = arg
          else:
              sys.stderr.write('unexpected argument\n')
              sys.exit(USAGE)
      if args:
! 	destination = args
!         if len(destination) % 2 != 0:
!             sys.stderr.write('unexpected argument\n')
!             sys.exit(USAGE)
!         else:
!             numrecipients = len(destination) / 2
!             recipients = destination[0:numrecipients]
!             users = destination[numrecipients:]
!     if not lmtp_host or not sender or not recipients:
          sys.stderr.write('required argument missing\n')
          sys.exit(USAGE)
  
      try:
!         CHUNKSIZE = 256 * 1024
!         # read in the first chunk of the message
!         
!         data = sys.stdin.read(CHUNKSIZE)
!         
!         if len(data) >= CHUNKSIZE:
!             process_message(spamd_host, spamd_port, lmtp_host, sender, recipients, users, data)
!         else: 
!             process_message(spamd_host, spamd_port, lmtp_host, sender, recipients, users, data)
! 
      except SystemExit, status:
          raise SystemExit, status
      except:
Ted Cabeen           http://www.pobox.com/~secabeen            [EMAIL PROTECTED] 
Check Website or Keyserver for PGP/GPG Key BA0349D2         [EMAIL PROTECTED]
"I have taken all knowledge to be my province." -F. Bacon  [EMAIL PROTECTED]
"Human kind cannot bear very much reality."-T.S.Eliot        [EMAIL PROTECTED]

Reply via email to