Eric Raymond added support for ESMTP protocol and corrected some typos
in comments and doc strings.
diff --git a/Lib/smtplib.py b/Lib/smtplib.py
index 6a12621..95c0f96 100755
--- a/Lib/smtplib.py
+++ b/Lib/smtplib.py
@@ -1,10 +1,13 @@
-"""SMTP Client class.
+#!/usr/bin/python
+"""SMTP/ESMTP client class.
 
 Author: The Dragon De Monsyne <dragondm@integral.org>
+ESMTP support, test code and doc fixes added by
+Eric S. Raymond <esr@thyrsus.com>
 
 (This was modified from the Python 1.5 library HTTP lib.)
 
-This should follow RFC 821. (it dosen't do esmtp (yet))
+This should follow RFC 821 (SMTP) and RFC 1869 (ESMTP).
 
 Example:
 
@@ -38,11 +41,16 @@
 SMTPServerDisconnected="Server not connected"
 SMTPSenderRefused="Sender address refused"
 SMTPRecipientsRefused="All Recipients refused"
-SMTPDataError="Error transmoitting message data"
+SMTPDataError="Error transmitting message data"
 
 class SMTP:
-    """This class manages a connection to an SMTP server."""
-    
+    """This class manages a connection to an SMTP or ESMTP server."""
+    debuglevel = 0
+    file = None
+    helo_resp = None
+    ehlo_resp = None
+    esmtp_features = []
+
     def __init__(self, host = '', port = 0):
         """Initialize a new instance.
 
@@ -51,9 +59,6 @@
         to connect.  By default, smtplib.SMTP_PORT is used.
 
         """
-        self.debuglevel = 0
-        self.file = None
-        self.helo_resp = None
         if host: self.connect(host, port)
     
     def set_debuglevel(self, debuglevel):
@@ -65,9 +70,18 @@
         """
         self.debuglevel = debuglevel
 
+    def verify(self, address):
+        """ SMTP 'verify' command. Checks for address validity. """
+        self.putcmd("vrfy", address)
+        return self.getreply()
+
     def connect(self, host='localhost', port = 0):
         """Connect to a host on a given port.
-          
+
+        If the hostname ends with a colon (`:') followed by a number,
+        that suffix will be stripped off and the number interpreted as
+        the port number to use.
+
         Note:  This method is automatically invoked by __init__,
         if a host is specified during instantiation.
 
@@ -101,14 +115,14 @@
         str = '%s %s%s' % (cmd, args, CRLF)
         self.send(str)
     
-    def getreply(self):
+    def getreply(self, linehook=None):
         """Get a reply from the server.
         
         Returns a tuple consisting of:
         - server response code (e.g. '250', or such, if all goes well)
-          Note: returns -1 if it can't read responce code.
+          Note: returns -1 if it can't read response code.
         - server response string corresponding to response code
-                (note : multiline responces converted to a single, 
+                (note : multiline responses converted to a single, 
                  multiline string)
         """
         resp=[]
@@ -121,6 +135,8 @@
             #check if multiline resp
             if line[3:4]!="-":
                 break
+            elif linehook:
+                linehook(line)
         try:
             errcode = string.atoi(code)
         except(ValueError):
@@ -132,7 +148,7 @@
         return errcode, errmsg
     
     def docmd(self, cmd, args=""):
-        """ Send a command, and return it's responce code """
+        """ Send a command, and return its response code """
         
         self.putcmd(cmd,args)
         (code,msg)=self.getreply()
@@ -150,6 +166,26 @@
         self.helo_resp=msg
         return code
 
+    def ehlo(self, name=''):
+        """ SMTP 'ehlo' command. Hostname to send for this command  
+        defaults to the FQDN of the local host """
+        name=string.strip(name)
+        if len(name)==0:
+                name=socket.gethostbyaddr(socket.gethostname())[0]
+        self.putcmd("ehlo",name)
+        (code,msg)=self.getreply(self.ehlo_hook)
+        self.ehlo_resp=msg
+        return code
+
+    def ehlo_hook(self, line):
+        # Interpret EHLO response lines
+        if line[4] in string.uppercase+string.digits:
+            self.esmtp_features.append(string.lower(string.strip(line)[4:]))
+
+    def has_option(self, opt):
+        """Does the server support a given SMTP option?"""
+        return opt in self.esmtp_features
+
     def help(self, args=''):
         """ SMTP 'help' command. Returns help text from server """
         self.putcmd("help", args)
@@ -162,13 +198,17 @@
         return code
 
     def noop(self):
-        """ SMTP 'noop' command. Dosen't do anything :> """
+        """ SMTP 'noop' command. Doesn't do anything :> """
         code=self.docmd("noop")
         return code
 
-    def mail(self,sender):
+    def mail(self,sender,options=[]):
         """ SMTP 'mail' command. Begins mail xfer session. """
-        self.putcmd("mail","from: %s" % sender)
+        if options:
+            options = " " + string.joinfields(options, ' ')
+        else:
+            options = ''
+        self.putcmd("mail from:", sender + options)
         return self.getreply()
 
     def rcpt(self,recip):
@@ -178,7 +218,7 @@
 
     def data(self,msg):
         """ SMTP 'DATA' command. Sends message data to server. 
-            Automatically quotes lines beginning with a period per rfc821 """
+            Automatically quotes lines beginning with a period per rfc821. """
         #quote periods in msg according to RFC821
         # ps, I don't know why I have to do it this way... doing: 
         # quotepat=re.compile(r"^[.]",re.M)
@@ -202,26 +242,30 @@
             if self.debuglevel >0 : print "data:", (code,msg)
             return code
 
-#some usefull methods
-    def sendmail(self,from_addr,to_addrs,msg):
+#some useful methods
+    def sendmail(self,from_addr,to_addrs,msg,options=[]):
         """ This command performs an entire mail transaction. 
             The arguments are: 
                - from_addr : The address sending this mail.
                - to_addrs :  a list of addresses to send this mail to
                - msg : the message to send. 
+               - encoding : list of ESMTP options (such as 8bitmime)
+
+	If there has been no previous EHLO or HELO command this session,
+	this method tries ESMTP EHLO first. If the server does ESMTP, message
+        size and each of the specified options will be passed to it (if the
+        option is in the feature set the server advertises).  If EHLO fails,
+        HELO will be tried and ESMTP options suppressed.
 
         This method will return normally if the mail is accepted for at least 
-        one recipiant.
-        Otherwise it will throw an exception (either SMTPSenderRefused,
-          SMTPRecipientsRefused, or SMTPDataError)
-
+        one recipient. Otherwise it will throw an exception (either
+        SMTPSenderRefused, SMTPRecipientsRefused, or SMTPDataError)
         That is, if this method does not throw an exception, then someone 
-        should get your mail.
-
-        It returns a dictionary , with one entry for each recipient that was 
+        should get your mail.  If this method does not throw an exception,
+        it returns a dictionary, with one entry for each recipient that was 
         refused. 
 
-        example:
+        Example:
       
          >>> import smtplib
          >>> s=smtplib.SMTP("localhost")
@@ -240,11 +284,19 @@
          code 550. If all addresses are accepted, then the method
          will return an empty dictionary.  
          """
-
-        if not self.helo_resp:
-            self.helo()
-        (code,resp)=self.mail(from_addr)
-        if code <>250:
+        if not self.helo_resp and not self.ehlo_resp:
+            if self.ehlo() >= 400:
+                self.helo()
+        if self.esmtp_features:
+            self.esmtp_features.append('7bit')
+        esmtp_opts = []
+        if 'size' in self.esmtp_features:
+            esmtp_opts.append("size=" + `len(msg)`)
+        for option in options:
+            if option in self.esmtp_features:
+                esmtp_opts.append(option)
+        (code,resp) = self.mail(from_addr, esmtp_opts)
+        if code <> 250:
             self.rset()
             raise SMTPSenderRefused
         senderrs={}
@@ -253,7 +305,7 @@
             if (code <> 250) and (code <> 251):
                 senderrs[each]=(code,resp)
         if len(senderrs)==len(to_addrs):
-            #th' server refused all our recipients
+            # the server refused all our recipients
             self.rset()
             raise SMTPRecipientsRefused
         code=self.data(msg)
@@ -275,5 +327,33 @@
 
 
     def quit(self):
+        """Terminate the SMTP session."""
         self.docmd("quit")
         self.close()
+
+# Test the sendmail method, which tests most of the others.
+# Note: This always sends to localhost.
+if __name__ == '__main__':
+    import sys, rfc822
+
+    def prompt(prompt):
+        sys.stdout.write(prompt + ": ")
+        return string.strip(sys.stdin.readline())
+
+    fromaddr = prompt("From")
+    toaddrs  = string.splitfields(prompt("To"), ',')
+    print "Enter message, end with ^D:"
+    msg = ''
+    while 1:
+        line = sys.stdin.readline()
+        if not line:
+            break
+        msg = msg + line
+    print "Message length is " + `len(msg)`
+
+    server = SMTP('localhost')
+    server.set_debuglevel(1)
+    server.sendmail(fromaddr, toaddrs, msg)
+    server.quit()
+
+