[Zope-Checkins] CVS: Zope3/lib/python/Zope/Server/SMTP - SMTPConfigurations.py:1.1.2.1 SMTPServer.py:1.1.2.2 SMTPServerChannel.py:1.1.2.2 SMTPStatusMessages.py:1.1.2.2

Stephan Richter srichter@cbu.edu
Mon, 8 Apr 2002 02:51:00 -0400


Update of /cvs-repository/Zope3/lib/python/Zope/Server/SMTP
In directory cvs.zope.org:/tmp/cvs-serv31265/SMTP

Modified Files:
      Tag: Zope3-Server-Branch
	SMTPServer.py SMTPServerChannel.py SMTPStatusMessages.py 
Added Files:
      Tag: Zope3-Server-Branch
	SMTPConfigurations.py 
Log Message:
This is the first working version of the SMTP server. There is still a lot
of work to be done, but it is a start. Now I need to stabalize the code and
write some tests. 

Also, Gerson Kunze (author of Shicks!), whose code I used as a template,
told me that he would be working on implementing ESMTP and a better SPAM 
filter, whcih should make the server even cooler.

I guess we should start discussing how a possible MailService could look
like. 


=== Added File Zope3/lib/python/Zope/Server/SMTP/SMTPConfigurations.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""

$Id: SMTPConfigurations.py,v 1.1.2.1 2002/04/08 06:50:27 srichter Exp $
"""

# ** Relay rules **

# Specify relay level.
#   true:   Allow relaying except from domains specified in DENY_RELAY_FROM
#           and to domains in DENY_RELAY_TO
#   false:  Deny relaying except from domains specified in ALLOW_RELAY_FROM
#           and to domains in ALLOW_RELAY_TO
RELAY_FROM = 1 
ACCEPT_RELAY_FROM = ('*cbu.edu', '*zope.org')
DENY_RELAY_FROM = ()

RELAY_TO = 1
ACCEPT_RELAY_TO = ('*.cbu.edu')
DENY_RELAY_TO = ()

# If specified all mail is forwarded to this server.
USE_RELAY_SERVER = ''

# When set to true, a local sender is only allowed to send, if the connection
# is coming from a local IP.
STRICT_RELAY_TEST = 0

# Define some standard mail accounts
ADMIN_ACCOUNT = 'foo'
UNKNOWN_ACCOUNT = 'unknown'

LOCAL_DOMAIN_NAME = '*cbu.edu'


=== Zope3/lib/python/Zope/Server/SMTP/SMTPServer.py 1.1.2.1 => 1.1.2.2 ===
 """
 import asyncore
-from FTPServerChannel import FTPServerChannel
+from SMTPServerChannel import SMTPServerChannel
+import SMTPConfigurations
 from Zope.Server.ServerBase import ServerBase
-from Zope.Server.Counter import Counter
 
 from Zope.Server.VFS.UnixFileSystem import UnixFileSystem 
 from Zope.Server.Authentication.DictionaryAuthentication import \
@@ -31,34 +31,26 @@
     channel_class = SMTPServerChannel
     SERVER_IDENT = 'Zope.Server.SMTPServer'
 
-
-    relay_smtp_server_name = 'mail.cbu.edu'
     storage = UnixFileSystem('/opt/ZopeMail')
     auth_source = DictionaryAuthentication({'foo': 'bar'})
-    refresh_relay_rules = 1
-    relay_rules = []
-    allow_unknown_receiver_default = 1
-    allow_unknown_sender_default = 1
-    admin_account = ""
-    default_local_domain = ""
-    unknown_account = ""
-    ip_address_range = None
-    
+    config = SMTPConfigurations
 
-    def __init__(self, ip, port, task_dispatcher=None, adj=None, start=1,
-                 hit_log=None, verbose=0, socket_map=None):
+    def __init__(self, ip, port, maildir, auth, task_dispatcher=None,
+                 adj=None, start=1, hit_log=None, verbose=0, socket_map=None):
         super(SMTPServer, self).__init__(ip, port, task_dispatcher,
                                          adj, start, hit_log,
                                          verbose, socket_map)
         
-        self.counter = Counter()
+        self.auth_source = auth
+        self.maildir = UnixFileSystem(maildir)
         
 
 if __name__ == '__main__':
     from Zope.Server.TaskThreads import ThreadedTaskDispatcher
     td = ThreadedTaskDispatcher()
     td.setThreadCount(4)
-    SMTPServer('', 8025, task_dispatcher=td)
+    auth_source = DictionaryAuthentication({'foo': 'bar'})
+    SMTPServer('', 25, '/var/mail', auth_source, task_dispatcher=td)
     try:
         while 1:
             asyncore.poll(5)


=== Zope3/lib/python/Zope/Server/SMTP/SMTPServerChannel.py 1.1.2.1 => 1.1.2.2 === (494/594 lines abridged)
 """
 
-from Zope.Server.ServerChannelBase import ServerChannelBase
-from SMTPimport status_msgs
-from SMTPTask import SMTPTask
+import time
+import fnmatch
+import socket
+
+from Zope.Server.LineReceiver.LineServerChannel import LineServerChannel
+from SMTPStatusMessages import status_msgs
 
 from ISMTPCommandHandler import ISMTPCommandHandler 
 
@@ -30,29 +33,73 @@
     __implements__ = ISMTPCommandHandler
 
     # Commands that are run in a separate thread
-    thread_commands = ('cmd_mail', 'cmd_vrfy', 'cmd_data')
+    # thread_commands = ('cmd_mail', 'cmd_vrfy', 'cmd_data')
 
     # Define the authentication status of the channel. Note that only the
     # "special commands" can be executed without having authenticated.
     authenticated = 1
 
     # Define the reply code for an unrecognized command
-    unknown_reply = (500, 0)
+    unknown_reply = 'ERR_CMD_UNKNOWN'
 
     # Define the status messages
     status_messages = status_msgs
 
+    # Defines the message terminator (a string sequence that signalizes the
+    # end of a message)
+    message_terminator = '.\r\n'
+
+    # Define the Date/Time format. The Python Mail parser is very strict
+    # about the format of this date.
+    datetime_format = '%a %b %d %H:%M:%S %Y'
+
 
     def __init__(self, server, conn, addr, adj=None, socket_map=None):
         super(SMTPServerChannel, self).__init__(server, conn, addr,
                                                adj, socket_map)
+
+        self._from = ''
+        self._to = []
+        self._message = ''
         
         self._sender_host = None

[-=- -=- -=- 494 lines omitted -=- -=- -=-]

+            name = name[1:-1]
+
+        try:
+            username, domain = address.split('@')
+        except:
+            username, domain = address, ''
+
+        return '"%s" <%s@%s>' %(username, username, domain) 
+        
+
+    def isLocalConnection(self):
+        name = ip2hostname(self.addr[0])
+        match = fnmatch.fnmatch(name, self.server.config.LOCAL_DOMAIN_NAME)
+        return match or name == 'localhost.localdomain'
+
+
+    def isLocalAddress(self, address):
+        # clean up the address
+        if address[0] == '<' or address[-1] == '>':
+            address = address[1:-1]
+
+        # Split the address into it user and domain component
+        try:
+            username, domain = address.split('@')
+        except:
+            username, domain = address, ''
+
+        if ( self.server.auth_source.hasUser(username) and
+             (domain.lower() == self.server.server_name or domain == '') ):
+            return 1
+
+        return 0
+        
+        
+
+def ip2hostname(ip, default=None):
+    """Resolves an IP into a hostname"""
+    try:
+        return socket.gethostbyaddr(ip)[0]
+    except socket.herror:
+        return default
+        
+
+
+def hostname2ip(hostname, default=None):
+    try:
+        return socket.gethostbyname(hostname)
+    except socket.gaierror:
+        return default
+        


=== Zope3/lib/python/Zope/Server/SMTP/SMTPStatusMessages.py 1.1.2.1 => 1.1.2.2 ===
 
 status_msgs = {
-    214: ("Help not available. RTFM!",),
-    220: ("Zope Service ready",),
-    221: ("SHICKS! Service closing transmission channel",),
-    250: ('OK',
-          '%s',              # Username and location
-          'Zope 3.0 ready.', # =
-          'SIZE',            # |
-          'VRFY',            # |
-          'HELP',),          # +--> These are all for HELO
-    251: ("User not local; will forward to <forward-path>",),
-    354: ("Start mail input; end with <CRLF>.<CRLF>",),
-    421: ("Zope Service not available",),
-    450: ("Requested mail action not taken: mailbox unavailable",),
-    500: ('Syntax error, command unrecognized"',),
-    501: ("Syntax error in parameters or arguments",),
-    502: ("Command not implemented",),
-    503: (" Bad sequence of commands",),
-    504: ("Command parameter not implemented",),
-    550: (" Requested action not taken: mailbox unknown",
-          'String does not match anything.'),
-    551: ("ACCESS DENIED.",),  
-    554: ("Transaction failed",),
+    'OK_HELP'         : '214 Help not available. RTFM!',
+    'OK_WELCOME'      : '220 %s Zope 3 SMTP Service ready; %s',
+    'OK_QUIT'         : '221 Closing transmission channel',
+    'OK_NOOP'         : '250 OK',
+    'OK_GREETING'     : '250 %s Hello %s [%s], pleased to meet you',
+    'OK_FROM_ACCPT'   : '250 Sender has been accepted',
+    'OK_TO_ACCPT'     : '250 Receiver has been accepted',
+    'OK_RESET'        : '250 Session reset',
+    'OK_VERIFY'       : '250 %s',
+    'OK_DATA_RECV'    : '250 Data received',
+    'OK_TSFR_START'   : '354 Start mail input; end with <CRLF>.<CRLF>',
+    
+    'ERR_CMD_UNKNOWN' : '500 "%s" Syntax error, command unrecognized',
+    'ERR_DOMAIN_REQ'  : '501 HELO requires domain address',
+    'ERR_MISS_FROM'   : '501 MAIL command without "FROM:"',
+    'ERR_MISS_TO'     : '501 RCPT command without "TO:"',
+    'ERR_USR_UNKNOWN' : '550 No user called "%s" known',
+    'ERR_FROM_DENIED' : '551 Access for sender %s denied',
+    'ERR_TO_DENIED'   : '551 Access for sender %s denied',
+    'ERR_ACC_DENIED'  : '551 Data transfer access denied',
     }
-