summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJens Vagelpohl <jens@netz.ooo>2005-02-12 13:42:53 +0000
committerJens Vagelpohl <jens@netz.ooo>2005-02-12 13:42:53 +0000
commitc7b1a85372ae7467be71a58d3dc7a895ed154282 (patch)
tree3480e7b2e18d8e8f9e32992fdff3edec6caaced1
parent8aa1dc86b96460bf3445808b5866b443b35b1236 (diff)
downloadZoftware-c7b1a85372ae7467be71a58d3dc7a895ed154282.zip
Zoftware-c7b1a85372ae7467be71a58d3dc7a895ed154282.tar.gz
This release changes the way the maildrop daemon process is started.
Command-line switches are no longer supported and all configuration is held in config.py. To start the daemon you must pass along the path to your config.py. * Features added: - The maildrop daemon now supports both TLS (Transport Layer Security) and authenticated SMTP.
-rw-r--r--CHANGES.txt10
-rw-r--r--MaildropHost.py18
-rw-r--r--config.py18
-rw-r--r--dtml/manageMaildropHost.dtml27
-rwxr-xr-xmaildrop/maildrop.py339
-rwxr-xr-xmaildrop/start_maildrop26
-rw-r--r--maildrop/start_maildrop.bat18
7 files changed, 253 insertions, 203 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index dc15113..f8ed95d 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -2,6 +2,16 @@ MaildropHost version and change information
1.10
+ This release changes the way the maildrop daemon process is started.
+ Command-line switches are no longer supported and all configuration
+ is held in config.py. To start the daemon you must pass along the
+ path to your config.py.
+
+ * Features added:
+
+ - The maildrop daemon now supports both TLS (Transport Layer Security)
+ and authenticated SMTP.
+
* Bugs fixed:
- More small improvements to the start_maildrop and stop_maildrop
diff --git a/MaildropHost.py b/MaildropHost.py
index f1f45dd..397d6a0 100644
--- a/MaildropHost.py
+++ b/MaildropHost.py
@@ -19,7 +19,14 @@ from AccessControl import ClassSecurityInfo
from Products.MailHost.MailHost import MailHost
# MaildropHost package imports
-from config import MAILDROP_HOME, SMTP_HOST, SMTP_PORT, POLLING, DEBUG
+from config import MAILDROP_HOME
+from config import MAILDROP_INTERVAL
+from config import MAILDROP_TLS
+from config import MAILDROP_LOGIN
+from config import MAILDROP_PASSWORD
+from config import SMTP_HOST
+from config import SMTP_PORT
+from config import DEBUG
MAILDROP_SPOOL = os.path.join(MAILDROP_HOME, 'spool')
@@ -58,9 +65,14 @@ class MaildropHost(MailHost):
# so they can be read just like the standard MailHost attributes
smtp_host = SMTP_HOST
smtp_port = SMTP_PORT
- debug = DEBUG
- polling = POLLING
+ debug = DEBUG and 'On' or 'Off'
+ polling = MAILDROP_INTERVAL
spool = MAILDROP_SPOOL
+ use_tls = ( (MAILDROP_TLS > 1 and 'Forced') or
+ (MAILDROP_TLS == 1 and 'Yes') or
+ 'No' )
+ login = MAILDROP_LOGIN or '(not set)'
+ password = MAILDROP_PASSWORD and '******' or '(not set)'
manage_options = (
( { 'label' : 'Edit', 'action' : 'manage_main'
diff --git a/config.py b/config.py
index 174d484..afb6d46 100644
--- a/config.py
+++ b/config.py
@@ -11,7 +11,7 @@ SMTP_HOST="localhost"
SMTP_PORT=25
# How long to wait between spool checks
-POLLING=120
+MAILDROP_INTERVAL=120
# Set debug mode
DEBUG=0
@@ -19,4 +19,18 @@ DEBUG=0
# Batch size for smtp-connection
# = 0 means bulk all mails at once
# > 0 means close/reopen connection after BATCH mails
-BATCH=0
+MAILDROP_BATCH=0
+
+# TLS usage. The values available are...
+# 0 : Don't try to us TLS
+# 1 : Try to use TLS if possible, but don't fail if TLS is not available
+# 2 : Force TLS and fail if TLS is not available
+# If a username/password is specified for the SMTP server, it is recommended
+# to set the value to "2" to prevent password sniffing.
+MAILDROP_TLS=0
+
+# SMTP Authentication
+# If the login and password are provided, authentication is attempted.
+# If the authentication attempt fails, mail processing stops. Beware.
+MAILDROP_LOGIN=""
+MAILDROP_PASSWORD=""
diff --git a/dtml/manageMaildropHost.dtml b/dtml/manageMaildropHost.dtml
index e9f6a32..98fbd26 100644
--- a/dtml/manageMaildropHost.dtml
+++ b/dtml/manageMaildropHost.dtml
@@ -87,6 +87,33 @@
<tr>
<td align="left" valign="top"><div class="form-label">
+ TLS usage
+ </div></td>
+ <td align="left" valign="top"><div class="form-text">
+ &dtml-use_tls;
+ </div></td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top"><div class="form-label">
+ SMTP Server Login
+ </div></td>
+ <td align="left" valign="top"><div class="form-text">
+ &dtml-login;
+ </div></td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top"><div class="form-label">
+ SMTP Server Password
+ </div></td>
+ <td align="left" valign="top"><div class="form-text">
+ &dtml-password;
+ </div></td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top"><div class="form-label">
Spool location
</div></td>
<td align="left" valign="top"><div class="form-text">
diff --git a/maildrop/maildrop.py b/maildrop/maildrop.py
index 5efc6de..89dc683 100755
--- a/maildrop/maildrop.py
+++ b/maildrop/maildrop.py
@@ -13,41 +13,12 @@ __version__ = "$Revision$"[11:-2]
usage = """
Maildrop service startup file
-Usage: maildrop.py [options]
+Usage: maildrop.py /path/to/config.py
-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
+import smtplib, os, sys, time, rfc822, imp
-DEBUG = 0
-MAILDROP_INTERVAL = 120
-SMTP_PORT = 25
-MAILDROP_BATCH = 0
FATAL_ERROR_CODES = ('500', '501', '502', '503', '504', '550', '551', '553')
MaildropError = 'Maildrop Error'
@@ -56,61 +27,59 @@ try:
if sys.version.split()[0] < '2.1':
raise MaildropError, 'Invalid python version %s' % sys.version.split[0]
- if len( sys.argv ) < 3:
+ if len( sys.argv ) < 2:
print usage
- opts, args = getopt.getopt( sys.argv[1:], 'h:i:s:d:b:p:' )
+ # IT IS EXPECTED THAT THE MODULE NAME IS 'config'
+ c_dir, c_file = os.path.split(sys.argv[1])
+ c_module = os.path.splitext(c_file)[0]
- 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
+ if c_module != 'config':
+ raise MaildropError, 'Invalid configuration file "%s"' % o_val
- MAILDROP_SPOOL = os.path.join( MAILDROP_HOME, 'spool' )
- if not os.path.isdir( MAILDROP_SPOOL ):
- os.mkdir( MAILDROP_SPOOL )
+ sys.path.append(c_dir)
- 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' )
+ try:
+ handle, path, description = imp.find_module(c_module)
+ module = imp.load_module(c_module, handle, path, description)
+ except ImportError:
+ raise MaildropError, 'Cannot load config from "%s"' % c_module
+
+ from config import MAILDROP_HOME
+ from config import MAILDROP_INTERVAL
+ from config import MAILDROP_BATCH
+ from config import MAILDROP_TLS
+ from config import MAILDROP_LOGIN
+ from config import MAILDROP_PASSWORD
+ from config import SMTP_HOST
+ from config import SMTP_PORT
+ from config import DEBUG
+
+ 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
+ try:
+ mail_server = smtplib.SMTP( SMTP_HOST, SMTP_PORT )
- if o_key == '-b':
- try:
- MAILDROP_BATCH = int(o_val)
- except ValueError:
- MAILDROP_BATCH = 0
+ if MAILDROP_TLS > 1:
+ mail_server.starttls()
- if o_key == '-d':
- if o_val not in (0, '0', 'False'):
- DEBUG = 1
+ if MAILDROP_LOGIN != '' and MAILDROP_PASSWORD != '':
+ mail_server.login(MAILDROP_LOGIN, MAILDROP_PASSWORD)
- try:
- mail_server = smtplib.SMTP( SMTP_HOST, SMTP_PORT )
mail_server.quit()
except:
+ if DEBUG: import traceback; traceback.print_exc()
msg = 'Invalid SMTP server "%s:%d"' % (SMTP_HOST, SMTP_PORT)
raise MaildropError, msg
-
except SystemExit: sys.exit( 0 )
except:
print usage
@@ -146,70 +115,110 @@ while 1:
# Are there any files in the spool directory?
to_be_sent = []
all_files = os.listdir( MAILDROP_SPOOL )
+ clean_files = [x for x in all_files
+ if not x.endswith('.lck')]
+ clean_files = [x for x in clean_files
+ if not '%s.lck' % x in all_files]
+ clean_files = [x for x in clean_files
+ if not os.path.isdir(os.path.join(MAILDROP_SPOOL, x))]
+
+ for file_name in clean_files:
+ file_path = os.path.join(MAILDROP_SPOOL, file_name)
+
+ # 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 )
+ smtp_server.ehlo()
+ 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
+
+ if MAILDROP_TLS > 0:
+ if ( MAILDROP_TLS > 1 and
+ not smtp_server.has_extn('starttls') ):
+ # Problem: TLS is required but the server does not offer it
+ # We stop processing here.
+ time_stamp = time.strftime( '%Y/%m/%d %H:%M:%S' )
+ err_msg = '!!!!! TLS unavailable 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
+
+ smtp_server.starttls()
- 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:] )
+ if MAILDROP_LOGIN != '' and MAILDROP_PASSWORD != '':
+ # Login is required to send mail
+ if not smtp_server.has_extn('auth'):
+ # The server does not offer authentication but we want it
+ # We stop processing here.
+ time_stamp = time.strftime( '%Y/%m/%d %H:%M:%S' )
+ err_msg = '!!!!! Authentication unavailable 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
- 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.
+ smtp_server.login(MAILDROP_LOGIN, MAILDROP_PASSWORD)
+ except smtplib.SMTPAuthenticationError:
+ # Authentication with the given credentials fails.
+ # We stop processing here.
time_stamp = time.strftime( '%Y/%m/%d %H:%M:%S' )
- err_msg = '!!!!! Connection error at %s' % time_stamp
+ err_msg = '!!!!! Authentication failed at %s' % time_stamp
finish_msg = '### Finished at %s' % time_stamp
log_file.write( err_msg )
if DEBUG: print err_msg
@@ -218,40 +227,40 @@ while 1:
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()
+ 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 )
diff --git a/maildrop/start_maildrop b/maildrop/start_maildrop
index e9e3d1a..5ed5d82 100755
--- a/maildrop/start_maildrop
+++ b/maildrop/start_maildrop
@@ -5,26 +5,16 @@
#
reldir=`dirname $0`
-# Source the main configuration file
-. $reldir/../config.py
+if [ $reldir = "." ]; then
+ reldir=`pwd`;
+fi
+
+# Config is one level back up, so find the parent
+parent=`dirname $reldir`
+CONFIG="$parent/config.py"
# Where is the python executable?
PYTHON="/usr/bin/python"
-export MAILDROP_HOME SMTP_HOST SMTP_PORT POLLING BATCH PYTHON
-
-exec $PYTHON $reldir/maildrop.py \
- -h $MAILDROP_HOME \
- -s $SMTP_HOST \
- -p $SMTP_PORT \
- -i $POLLING \
- -b $BATCH \
- -d $DEBUG
-
-
-# NOTE:
-#
-# If you add the "-d" flag to the flags used to invoke the
-# maildrop.py script then all log output will be written to
-# the terminal along with the log file.
+exec $PYTHON $reldir/maildrop.py "$CONFIG"
diff --git a/maildrop/start_maildrop.bat b/maildrop/start_maildrop.bat
index 6573fe5..ef6ce83 100644
--- a/maildrop/start_maildrop.bat
+++ b/maildrop/start_maildrop.bat
@@ -4,24 +4,12 @@ rem Developed and graciously contributed by Chris Beaven
rem You must change the values to reflect your own preferences
rem ***
-rem Set the maildrop main directory
-set maildrophome=C:\Program Files\Zope\lib\Python\Products\MaildropHost\maildrop
-
-rem What SMTP server will maildrop use?
-set smtp=smtp.myhost.com
-
-rem What SMTP port will maildrop use?
-set smtp_port=25
-
-rem How long to wait between spool checkups? (in seconds)
-set pollinginterval=120
+rem Set the maildrop configuration file
+set maildropcfg=C:\Program Files\Zope\lib\Python\Products\MaildropHost\config.py
rem Where is the python executable?
set pythonexe=C:\Program Files\Zope\bin\python.exe
-rem Debug mode
-set debugmode=0
-
rem ----------- No changes needed below this line ----------
-"%pythonexe%" "%maildrophome%/maildrop.py" -h "%maildrophome%" -s %smtp% -p %smtp_port% -i %pollinginterval% -d %debugmode%
+"%pythonexe%" "%maildrophome%/maildrop.py" "%maildropcfg%"
pause