dataflake.org

Home Documentation Software Old Stuff

Drop mail from mailspool if smtp-server replies with fatal error code (Resolved)

Request MaildropHost -- feature request -- by Maik Jablonski
Posted on Sep 22, 2004 5:07 pm
Subscribe

Enter your email address to receive mail on every change to this issue.

Entries (Latest first)


  Resolve by Jens Vagelpohl on Sep 24, 2004 4:10 am
  Thanks a lot Maik - the patch is applied and version 1.8 has just been released.

jens
 

  Comment by Maik Jablonski on Sep 24, 2004 3:52 am
  Hi,

anbei der "unified" diff gegen die aktuelle Version aus dem CVS und zur
Sicherheit nochmal die geänderte maildrop.py, damit Du evtl. selbst
nochmal einen diff machen kannst. Vielleicht lerne ich das ja nochmal
eines Tages mit den diffs..;)

Die Variante läuft bei mir jetzt seit ein paar Tagen ohne Probleme auf
meinem Server (mit Sendmail). Ob andere MTAs da genauso reibungsfrei mit
arbeiten, würde ich jetzt mal schätzen, weil eigentlich Fehlercodes aus
dem 500-Raum einheitlich als "unauflösbar problematisch" in den RFCs
definiert sind.

Auf jeden Fall bin ich jetzt haufenweise Spam und Unsinn aus den Logs
los, der sonst alle 2 Minuten wieder und wieder versucht wurde...:)

Grüße, Maik

--
Maik Jablonski - Universität Bielefeld - Zentrum für Lehrerbildung
http://www.zfl.uni-bielefeld.de/personal/mjablonski/
phone://+49.(0)521.106.4234

--- /mnt/d/downloads/maildrop.py 2004-09-22 23:02:55.197974400 +0200
+++ maildrop.py 2004-09-22 23:00:39.252494400 +0200
@@ -8,7 +8,7 @@
# LICENSE.txt for the terms of this license.
#
#####################################################################
-__version__ = "$Revision: 1.9 $"[11:-2]
+__version__ = "$Revision: 1.8 $"[11:-2]

usage = """
Maildrop service startup file
@@ -50,6 +50,8 @@
MAILDROP_BATCH = 0
MaildropError = 'Maildrop Error'

+FATAL_ERROR_CODES = ["500","501","502","503","504","550","551","553"]
+
try:
if sys.version.split()[0] < '2.1':
raise MaildropError, 'Invalid python version %s' % sys.version.split[0]
@@ -229,6 +231,12 @@
smtp_server.sendmail( h_from, h_to_list, h_body )
stat = 'OK'
os.remove( mail_dict.get( 'file_path' ) )
+ except smtplib.SMTPRecipientsRefused, e:
+ for (addr, error) in e.recipients.items():
+ stat = 'FATAL: ', str(e)
+ if str(error[0]) in FATAL_ERROR_CODES:
+ os.remove( mail_dict.get( 'file_path' ) )
+ break
except smtplib.SMTPException, e:
stat = 'BAD: ', str(e)


#!/usr/bin/env python2.1

#####################################################################
#
# maildrop A daemon to handle mail delivery for the MaildropHost
#
# This software is governed by a license. See
# LICENSE.txt for the terms of this license.
#
#####################################################################
__version__ = "$Revision: 1.8 $"[11:-2]

usage = """
Maildrop service startup file

Usage: maildrop.py [options]

Options:

- d

Debug mode: All output will be written to the terminal

- h

Maildrop home (Must be specified!)

- s

SMTP host to be used (Must be specified!)

- p

SMTP port to be used (defaults to 25)

- i

Polling interval in seconds

- b

Drop only x mails in one smtp-connection (defaults to 0 = all mails at once)
"""

import getopt, smtplib, os, sys, time, rfc822

DEBUG = 0
MAILDROP_INTERVAL = 120
SMTP_PORT = 25
MAILDROP_BATCH = 0
MaildropError = 'Maildrop Error'

FATAL_ERROR_CODES = ["500","501","502","503","504","550","551","553"]

try:
if sys.version.split()[0] < '2.1':
raise MaildropError, 'Invalid python version %s' % sys.version.split[0]

if len( sys.argv ) < 3:
print usage

opts, args = getopt.getopt( sys.argv[1:], 'h:i:s:d:b:p:' )

for o_key, o_val in opts:
if o_key == '-h':
MAILDROP_HOME = o_val
if not os.path.isdir( MAILDROP_HOME ):
raise MaildropError, 'Invalid maildrop home "%s"' % o_val

MAILDROP_SPOOL = os.path.join( MAILDROP_HOME, 'spool' )
if not os.path.isdir( MAILDROP_SPOOL ):
os.mkdir( MAILDROP_SPOOL )

log_home = os.path.join( MAILDROP_HOME, 'var' )
if not os.path.isdir( log_home ):
os.mkdir( log_home )
LOG_FILE = os.path.join( log_home, 'maildrop.log' )

if o_key == '-s':
SMTP_HOST = o_val

if o_key == '-p':
try:
SMTP_PORT = int(o_val)
except ValueError:
SMTP_PORT = 25

if o_key == '-i':
try:
MAILDROP_INTERVAL = int( o_val )
except:
msg = 'Invalid Maildrop interval "%s"' % str( o_val )
raise MaildropError, msg

if o_key == '-b':
try:
MAILDROP_BATCH = int(o_val)
except ValueError:
MAILDROP_BATCH = 0

if o_key == '-d':
if o_val not in (0, '0', 'False'):
DEBUG = 1

try:
mail_server = smtplib.SMTP( SMTP_HOST, SMTP_PORT )
mail_server.quit()
except:
msg = 'Invalid SMTP server "%s:%d"' % (SMTP_HOST, SMTP_PORT)
raise MaildropError, msg



except SystemExit: sys.exit( 0 )
except:
print usage
print
print "%s: %s" % ( sys.exc_type, sys.exc_value )
print
sys.exit( 1 )

try:
ppid, pid = os.getppid(), os.getpid()
if ppid == 1:
ppid = pid
except:
pass # getpid not supported
else:
pid_home = os.path.join( MAILDROP_HOME, 'var' )
if not os.path.isdir( pid_home ):
os.mkdir( pid_home )

pidfile_path = os.path.join( pid_home, 'maildrop.pid' )
pid_file = open( pidfile_path, 'w' )
pid_file.write( '%s %s' % ( pid, ppid ) )
pid_file.close()

if DEBUG:
# PID still might not exist so it's inside a try
try:
print 'Maildrop started with PID %s' % str( pid )
except:
print 'Maildrop started with unknown PID'

while 1:
# Are there any files in the spool directory?
to_be_sent = []
all_files = os.listdir( MAILDROP_SPOOL )

if len( all_files ) > 0:
for file_name in all_files:
f_name, f_ext = os.path.splitext( file_name )

# Exclude lock files
if f_ext == '.lck':
continue

# Exclude files that have locked files
if f_name + '.lck' in all_files:
continue

# Exclude directories
file_path = os.path.join( MAILDROP_SPOOL, file_name )
if os.path.isdir( file_path ):
continue

# Read in file
file_handle = open( file_path, 'r' )
file_contents = file_handle.read()
file_handle.close()

# Is this a real mail turd?
if not file_contents.startswith( '##To:' ):
continue

# Parse and handle content (mail it out)
mail_dict = {}
mail_dict['file_path'] = file_path
file_lines = file_contents.split( '\n' )

for i in range( len( file_lines ) ):
if file_lines[i].startswith( '##' ):
header_line = file_lines[i][2:]
header_key, header_val = header_line.split( ':', 1 )
mail_dict[header_key] = header_val
else:
mail_dict['body'] = '\n'.join( file_lines[i:] )
break

to_be_sent.append( mail_dict )

if len( to_be_sent ) > 0:
# Open the log file
time_stamp = time.strftime( '%Y/%m/%d %H:%M:%S' )
log_file = open( LOG_FILE, 'a' )
msg = '\n### Started at %s...' % time_stamp
log_file.write( msg )
if DEBUG: print msg

while len( to_be_sent ) > 0:
if (MAILDROP_BATCH == 0) or (MAILDROP_BATCH > len(to_be_sent)):
batch = len(to_be_sent)
else:
batch = MAILDROP_BATCH

# Send mail
try:
smtp_server = smtplib.SMTP( SMTP_HOST, SMTP_PORT )
except smtplib.SMTPConnectError:
# SMTP server did not respond. Log it and stop processing.
time_stamp = time.strftime( '%Y/%m/%d %H:%M:%S' )
err_msg = '!!!!! Connection error at %s' % time_stamp
finish_msg = '### Finished at %s' % time_stamp
log_file.write( err_msg )
if DEBUG: print err_msg
log_file.write( finish_msg )
if DEBUG: print finish_msg
log_file.close()
break

for mail_dict in to_be_sent[0:batch]:
# Create mail and send it off
h_from = mail_dict.get( 'From' )
h_to = mail_dict.get( 'To' )
h_to_list = []
for item in rfc822.AddressList(h_to):
h_to_list.append(item[1])
h_body = mail_dict.get( 'body' )

try:
smtp_server.sendmail( h_from, h_to_list, h_body )
stat = 'OK'
os.remove( mail_dict.get( 'file_path' ) )
except smtplib.SMTPRecipientsRefused, e:
for (addr, error) in e.recipients.items():
stat = 'FATAL: ', str(e)
if str(error[0]) in FATAL_ERROR_CODES:
os.remove( mail_dict.get( 'file_path' ) )
break
except smtplib.SMTPException, e:
stat = 'BAD: ', str(e)

mail_msg = '\n%s\t %s' % ( stat, h_to )
log_file.write( mail_msg )
if DEBUG: print mail_msg
log_file.flush()

to_be_sent = to_be_sent[batch:]
smtp_server.quit()

time_stamp = time.strftime( '%Y/%m/%d %H:%M:%S' )
finish_msg = '\n### Finished at %s\n' % time_stamp
log_file.write( finish_msg )
if DEBUG: print finish_msg
log_file.close()

time.sleep( MAILDROP_INTERVAL )
 

  Accept by Jens Vagelpohl on Sep 24, 2004 3:39 am
  Hi Maik,

Could you send me a complete unified diff (do "cvs diff -u maildrop.py") that is usable as input for the patch utility? This diff cannot be used for that and if I type this stuff in manually I *will* mistype somehow. Since there are no real "tests" in the package and I want to release it as a new version with this feature please make sure I get a precise diff against CVS HEAD, preferably from a working installation that has been tested by you.

jens
 

  Initial Request by Maik Jablonski on Sep 22, 2004 5:07 pm
  Sometimes mails with unresolvable recipients make it into the maildrop-queue. If the mail cannot be delivered because the mail-server can't handle the adress (because it's syntactically incorrect like horst.köhler@domain.com), the mail will be requeued and resent again and again.

The following patch to maildrop/maildrop.py fixes this for fatal error codes from the smtp-server.

52a53,54
> FATAL_ERROR_CODES = ["500","501","502","503","504","550","551","553"]
>
231a234,239
> except smtplib.SMTPRecipientsRefused, e:
> for (addr, error) in e.recipients.items():
> stat = 'FATAL: ', str(e)
> if str(error[0]) in FATAL_ERROR_CODES:
> os.remove( mail_dict.get( 'file_path' ) )
> break