python-2.5.2/win32/Lib/smtplib.py
changeset 0 ae805ac0140d
equal deleted inserted replaced
-1:000000000000 0:ae805ac0140d
       
     1 #! /usr/bin/env python
       
     2 
       
     3 '''SMTP/ESMTP client class.
       
     4 
       
     5 This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP
       
     6 Authentication) and RFC 2487 (Secure SMTP over TLS).
       
     7 
       
     8 Notes:
       
     9 
       
    10 Please remember, when doing ESMTP, that the names of the SMTP service
       
    11 extensions are NOT the same thing as the option keywords for the RCPT
       
    12 and MAIL commands!
       
    13 
       
    14 Example:
       
    15 
       
    16   >>> import smtplib
       
    17   >>> s=smtplib.SMTP("localhost")
       
    18   >>> print s.help()
       
    19   This is Sendmail version 8.8.4
       
    20   Topics:
       
    21       HELO    EHLO    MAIL    RCPT    DATA
       
    22       RSET    NOOP    QUIT    HELP    VRFY
       
    23       EXPN    VERB    ETRN    DSN
       
    24   For more info use "HELP <topic>".
       
    25   To report bugs in the implementation send email to
       
    26       sendmail-bugs@sendmail.org.
       
    27   For local information send email to Postmaster at your site.
       
    28   End of HELP info
       
    29   >>> s.putcmd("vrfy","someone@here")
       
    30   >>> s.getreply()
       
    31   (250, "Somebody OverHere <somebody@here.my.org>")
       
    32   >>> s.quit()
       
    33 '''
       
    34 
       
    35 # Author: The Dragon De Monsyne <dragondm@integral.org>
       
    36 # ESMTP support, test code and doc fixes added by
       
    37 #     Eric S. Raymond <esr@thyrsus.com>
       
    38 # Better RFC 821 compliance (MAIL and RCPT, and CRLF in data)
       
    39 #     by Carey Evans <c.evans@clear.net.nz>, for picky mail servers.
       
    40 # RFC 2554 (authentication) support by Gerhard Haering <gerhard@bigfoot.de>.
       
    41 #
       
    42 # This was modified from the Python 1.5 library HTTP lib.
       
    43 
       
    44 import socket
       
    45 import re
       
    46 import email.Utils
       
    47 import base64
       
    48 import hmac
       
    49 from email.base64MIME import encode as encode_base64
       
    50 from sys import stderr
       
    51 
       
    52 __all__ = ["SMTPException","SMTPServerDisconnected","SMTPResponseException",
       
    53            "SMTPSenderRefused","SMTPRecipientsRefused","SMTPDataError",
       
    54            "SMTPConnectError","SMTPHeloError","SMTPAuthenticationError",
       
    55            "quoteaddr","quotedata","SMTP"]
       
    56 
       
    57 SMTP_PORT = 25
       
    58 CRLF="\r\n"
       
    59 
       
    60 OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I)
       
    61 
       
    62 # Exception classes used by this module.
       
    63 class SMTPException(Exception):
       
    64     """Base class for all exceptions raised by this module."""
       
    65 
       
    66 class SMTPServerDisconnected(SMTPException):
       
    67     """Not connected to any SMTP server.
       
    68 
       
    69     This exception is raised when the server unexpectedly disconnects,
       
    70     or when an attempt is made to use the SMTP instance before
       
    71     connecting it to a server.
       
    72     """
       
    73 
       
    74 class SMTPResponseException(SMTPException):
       
    75     """Base class for all exceptions that include an SMTP error code.
       
    76 
       
    77     These exceptions are generated in some instances when the SMTP
       
    78     server returns an error code.  The error code is stored in the
       
    79     `smtp_code' attribute of the error, and the `smtp_error' attribute
       
    80     is set to the error message.
       
    81     """
       
    82 
       
    83     def __init__(self, code, msg):
       
    84         self.smtp_code = code
       
    85         self.smtp_error = msg
       
    86         self.args = (code, msg)
       
    87 
       
    88 class SMTPSenderRefused(SMTPResponseException):
       
    89     """Sender address refused.
       
    90 
       
    91     In addition to the attributes set by on all SMTPResponseException
       
    92     exceptions, this sets `sender' to the string that the SMTP refused.
       
    93     """
       
    94 
       
    95     def __init__(self, code, msg, sender):
       
    96         self.smtp_code = code
       
    97         self.smtp_error = msg
       
    98         self.sender = sender
       
    99         self.args = (code, msg, sender)
       
   100 
       
   101 class SMTPRecipientsRefused(SMTPException):
       
   102     """All recipient addresses refused.
       
   103 
       
   104     The errors for each recipient are accessible through the attribute
       
   105     'recipients', which is a dictionary of exactly the same sort as
       
   106     SMTP.sendmail() returns.
       
   107     """
       
   108 
       
   109     def __init__(self, recipients):
       
   110         self.recipients = recipients
       
   111         self.args = ( recipients,)
       
   112 
       
   113 
       
   114 class SMTPDataError(SMTPResponseException):
       
   115     """The SMTP server didn't accept the data."""
       
   116 
       
   117 class SMTPConnectError(SMTPResponseException):
       
   118     """Error during connection establishment."""
       
   119 
       
   120 class SMTPHeloError(SMTPResponseException):
       
   121     """The server refused our HELO reply."""
       
   122 
       
   123 class SMTPAuthenticationError(SMTPResponseException):
       
   124     """Authentication error.
       
   125 
       
   126     Most probably the server didn't accept the username/password
       
   127     combination provided.
       
   128     """
       
   129 
       
   130 class SSLFakeSocket:
       
   131     """A fake socket object that really wraps a SSLObject.
       
   132 
       
   133     It only supports what is needed in smtplib.
       
   134     """
       
   135     def __init__(self, realsock, sslobj):
       
   136         self.realsock = realsock
       
   137         self.sslobj = sslobj
       
   138 
       
   139     def send(self, str):
       
   140         self.sslobj.write(str)
       
   141         return len(str)
       
   142 
       
   143     sendall = send
       
   144 
       
   145     def close(self):
       
   146         self.realsock.close()
       
   147 
       
   148 class SSLFakeFile:
       
   149     """A fake file like object that really wraps a SSLObject.
       
   150 
       
   151     It only supports what is needed in smtplib.
       
   152     """
       
   153     def __init__(self, sslobj):
       
   154         self.sslobj = sslobj
       
   155 
       
   156     def readline(self):
       
   157         str = ""
       
   158         chr = None
       
   159         while chr != "\n":
       
   160             chr = self.sslobj.read(1)
       
   161             str += chr
       
   162         return str
       
   163 
       
   164     def close(self):
       
   165         pass
       
   166 
       
   167 def quoteaddr(addr):
       
   168     """Quote a subset of the email addresses defined by RFC 821.
       
   169 
       
   170     Should be able to handle anything rfc822.parseaddr can handle.
       
   171     """
       
   172     m = (None, None)
       
   173     try:
       
   174         m = email.Utils.parseaddr(addr)[1]
       
   175     except AttributeError:
       
   176         pass
       
   177     if m == (None, None): # Indicates parse failure or AttributeError
       
   178         # something weird here.. punt -ddm
       
   179         return "<%s>" % addr
       
   180     elif m is None:
       
   181         # the sender wants an empty return address
       
   182         return "<>"
       
   183     else:
       
   184         return "<%s>" % m
       
   185 
       
   186 def quotedata(data):
       
   187     """Quote data for email.
       
   188 
       
   189     Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
       
   190     Internet CRLF end-of-line.
       
   191     """
       
   192     return re.sub(r'(?m)^\.', '..',
       
   193         re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data))
       
   194 
       
   195 
       
   196 class SMTP:
       
   197     """This class manages a connection to an SMTP or ESMTP server.
       
   198     SMTP Objects:
       
   199         SMTP objects have the following attributes:
       
   200             helo_resp
       
   201                 This is the message given by the server in response to the
       
   202                 most recent HELO command.
       
   203 
       
   204             ehlo_resp
       
   205                 This is the message given by the server in response to the
       
   206                 most recent EHLO command. This is usually multiline.
       
   207 
       
   208             does_esmtp
       
   209                 This is a True value _after you do an EHLO command_, if the
       
   210                 server supports ESMTP.
       
   211 
       
   212             esmtp_features
       
   213                 This is a dictionary, which, if the server supports ESMTP,
       
   214                 will _after you do an EHLO command_, contain the names of the
       
   215                 SMTP service extensions this server supports, and their
       
   216                 parameters (if any).
       
   217 
       
   218                 Note, all extension names are mapped to lower case in the
       
   219                 dictionary.
       
   220 
       
   221         See each method's docstrings for details.  In general, there is a
       
   222         method of the same name to perform each SMTP command.  There is also a
       
   223         method called 'sendmail' that will do an entire mail transaction.
       
   224         """
       
   225     debuglevel = 0
       
   226     file = None
       
   227     helo_resp = None
       
   228     ehlo_resp = None
       
   229     does_esmtp = 0
       
   230 
       
   231     def __init__(self, host = '', port = 0, local_hostname = None):
       
   232         """Initialize a new instance.
       
   233 
       
   234         If specified, `host' is the name of the remote host to which to
       
   235         connect.  If specified, `port' specifies the port to which to connect.
       
   236         By default, smtplib.SMTP_PORT is used.  An SMTPConnectError is raised
       
   237         if the specified `host' doesn't respond correctly.  If specified,
       
   238         `local_hostname` is used as the FQDN of the local host.  By default,
       
   239         the local hostname is found using socket.getfqdn().
       
   240 
       
   241         """
       
   242         self.esmtp_features = {}
       
   243         if host:
       
   244             (code, msg) = self.connect(host, port)
       
   245             if code != 220:
       
   246                 raise SMTPConnectError(code, msg)
       
   247         if local_hostname is not None:
       
   248             self.local_hostname = local_hostname
       
   249         else:
       
   250             # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and
       
   251             # if that can't be calculated, that we should use a domain literal
       
   252             # instead (essentially an encoded IP address like [A.B.C.D]).
       
   253             fqdn = socket.getfqdn()
       
   254             if '.' in fqdn:
       
   255                 self.local_hostname = fqdn
       
   256             else:
       
   257                 # We can't find an fqdn hostname, so use a domain literal
       
   258                 addr = '127.0.0.1'
       
   259                 try:
       
   260                     addr = socket.gethostbyname(socket.gethostname())
       
   261                 except socket.gaierror:
       
   262                     pass
       
   263                 self.local_hostname = '[%s]' % addr
       
   264 
       
   265     def set_debuglevel(self, debuglevel):
       
   266         """Set the debug output level.
       
   267 
       
   268         A non-false value results in debug messages for connection and for all
       
   269         messages sent to and received from the server.
       
   270 
       
   271         """
       
   272         self.debuglevel = debuglevel
       
   273 
       
   274     def connect(self, host='localhost', port = 0):
       
   275         """Connect to a host on a given port.
       
   276 
       
   277         If the hostname ends with a colon (`:') followed by a number, and
       
   278         there is no port specified, that suffix will be stripped off and the
       
   279         number interpreted as the port number to use.
       
   280 
       
   281         Note: This method is automatically invoked by __init__, if a host is
       
   282         specified during instantiation.
       
   283 
       
   284         """
       
   285         if not port and (host.find(':') == host.rfind(':')):
       
   286             i = host.rfind(':')
       
   287             if i >= 0:
       
   288                 host, port = host[:i], host[i+1:]
       
   289                 try: port = int(port)
       
   290                 except ValueError:
       
   291                     raise socket.error, "nonnumeric port"
       
   292         if not port: port = SMTP_PORT
       
   293         if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
       
   294         msg = "getaddrinfo returns an empty list"
       
   295         self.sock = None
       
   296         for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
       
   297             af, socktype, proto, canonname, sa = res
       
   298             try:
       
   299                 self.sock = socket.socket(af, socktype, proto)
       
   300                 if self.debuglevel > 0: print>>stderr, 'connect:', sa
       
   301                 self.sock.connect(sa)
       
   302             except socket.error, msg:
       
   303                 if self.debuglevel > 0: print>>stderr, 'connect fail:', msg
       
   304                 if self.sock:
       
   305                     self.sock.close()
       
   306                 self.sock = None
       
   307                 continue
       
   308             break
       
   309         if not self.sock:
       
   310             raise socket.error, msg
       
   311         (code, msg) = self.getreply()
       
   312         if self.debuglevel > 0: print>>stderr, "connect:", msg
       
   313         return (code, msg)
       
   314 
       
   315     def send(self, str):
       
   316         """Send `str' to the server."""
       
   317         if self.debuglevel > 0: print>>stderr, 'send:', repr(str)
       
   318         if self.sock:
       
   319             try:
       
   320                 self.sock.sendall(str)
       
   321             except socket.error:
       
   322                 self.close()
       
   323                 raise SMTPServerDisconnected('Server not connected')
       
   324         else:
       
   325             raise SMTPServerDisconnected('please run connect() first')
       
   326 
       
   327     def putcmd(self, cmd, args=""):
       
   328         """Send a command to the server."""
       
   329         if args == "":
       
   330             str = '%s%s' % (cmd, CRLF)
       
   331         else:
       
   332             str = '%s %s%s' % (cmd, args, CRLF)
       
   333         self.send(str)
       
   334 
       
   335     def getreply(self):
       
   336         """Get a reply from the server.
       
   337 
       
   338         Returns a tuple consisting of:
       
   339 
       
   340           - server response code (e.g. '250', or such, if all goes well)
       
   341             Note: returns -1 if it can't read response code.
       
   342 
       
   343           - server response string corresponding to response code (multiline
       
   344             responses are converted to a single, multiline string).
       
   345 
       
   346         Raises SMTPServerDisconnected if end-of-file is reached.
       
   347         """
       
   348         resp=[]
       
   349         if self.file is None:
       
   350             self.file = self.sock.makefile('rb')
       
   351         while 1:
       
   352             line = self.file.readline()
       
   353             if line == '':
       
   354                 self.close()
       
   355                 raise SMTPServerDisconnected("Connection unexpectedly closed")
       
   356             if self.debuglevel > 0: print>>stderr, 'reply:', repr(line)
       
   357             resp.append(line[4:].strip())
       
   358             code=line[:3]
       
   359             # Check that the error code is syntactically correct.
       
   360             # Don't attempt to read a continuation line if it is broken.
       
   361             try:
       
   362                 errcode = int(code)
       
   363             except ValueError:
       
   364                 errcode = -1
       
   365                 break
       
   366             # Check if multiline response.
       
   367             if line[3:4]!="-":
       
   368                 break
       
   369 
       
   370         errmsg = "\n".join(resp)
       
   371         if self.debuglevel > 0:
       
   372             print>>stderr, 'reply: retcode (%s); Msg: %s' % (errcode,errmsg)
       
   373         return errcode, errmsg
       
   374 
       
   375     def docmd(self, cmd, args=""):
       
   376         """Send a command, and return its response code."""
       
   377         self.putcmd(cmd,args)
       
   378         return self.getreply()
       
   379 
       
   380     # std smtp commands
       
   381     def helo(self, name=''):
       
   382         """SMTP 'helo' command.
       
   383         Hostname to send for this command defaults to the FQDN of the local
       
   384         host.
       
   385         """
       
   386         self.putcmd("helo", name or self.local_hostname)
       
   387         (code,msg)=self.getreply()
       
   388         self.helo_resp=msg
       
   389         return (code,msg)
       
   390 
       
   391     def ehlo(self, name=''):
       
   392         """ SMTP 'ehlo' command.
       
   393         Hostname to send for this command defaults to the FQDN of the local
       
   394         host.
       
   395         """
       
   396         self.esmtp_features = {}
       
   397         self.putcmd("ehlo", name or self.local_hostname)
       
   398         (code,msg)=self.getreply()
       
   399         # According to RFC1869 some (badly written)
       
   400         # MTA's will disconnect on an ehlo. Toss an exception if
       
   401         # that happens -ddm
       
   402         if code == -1 and len(msg) == 0:
       
   403             self.close()
       
   404             raise SMTPServerDisconnected("Server not connected")
       
   405         self.ehlo_resp=msg
       
   406         if code != 250:
       
   407             return (code,msg)
       
   408         self.does_esmtp=1
       
   409         #parse the ehlo response -ddm
       
   410         resp=self.ehlo_resp.split('\n')
       
   411         del resp[0]
       
   412         for each in resp:
       
   413             # To be able to communicate with as many SMTP servers as possible,
       
   414             # we have to take the old-style auth advertisement into account,
       
   415             # because:
       
   416             # 1) Else our SMTP feature parser gets confused.
       
   417             # 2) There are some servers that only advertise the auth methods we
       
   418             #    support using the old style.
       
   419             auth_match = OLDSTYLE_AUTH.match(each)
       
   420             if auth_match:
       
   421                 # This doesn't remove duplicates, but that's no problem
       
   422                 self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \
       
   423                         + " " + auth_match.groups(0)[0]
       
   424                 continue
       
   425 
       
   426             # RFC 1869 requires a space between ehlo keyword and parameters.
       
   427             # It's actually stricter, in that only spaces are allowed between
       
   428             # parameters, but were not going to check for that here.  Note
       
   429             # that the space isn't present if there are no parameters.
       
   430             m=re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?',each)
       
   431             if m:
       
   432                 feature=m.group("feature").lower()
       
   433                 params=m.string[m.end("feature"):].strip()
       
   434                 if feature == "auth":
       
   435                     self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \
       
   436                             + " " + params
       
   437                 else:
       
   438                     self.esmtp_features[feature]=params
       
   439         return (code,msg)
       
   440 
       
   441     def has_extn(self, opt):
       
   442         """Does the server support a given SMTP service extension?"""
       
   443         return opt.lower() in self.esmtp_features
       
   444 
       
   445     def help(self, args=''):
       
   446         """SMTP 'help' command.
       
   447         Returns help text from server."""
       
   448         self.putcmd("help", args)
       
   449         return self.getreply()[1]
       
   450 
       
   451     def rset(self):
       
   452         """SMTP 'rset' command -- resets session."""
       
   453         return self.docmd("rset")
       
   454 
       
   455     def noop(self):
       
   456         """SMTP 'noop' command -- doesn't do anything :>"""
       
   457         return self.docmd("noop")
       
   458 
       
   459     def mail(self,sender,options=[]):
       
   460         """SMTP 'mail' command -- begins mail xfer session."""
       
   461         optionlist = ''
       
   462         if options and self.does_esmtp:
       
   463             optionlist = ' ' + ' '.join(options)
       
   464         self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender) ,optionlist))
       
   465         return self.getreply()
       
   466 
       
   467     def rcpt(self,recip,options=[]):
       
   468         """SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
       
   469         optionlist = ''
       
   470         if options and self.does_esmtp:
       
   471             optionlist = ' ' + ' '.join(options)
       
   472         self.putcmd("rcpt","TO:%s%s" % (quoteaddr(recip),optionlist))
       
   473         return self.getreply()
       
   474 
       
   475     def data(self,msg):
       
   476         """SMTP 'DATA' command -- sends message data to server.
       
   477 
       
   478         Automatically quotes lines beginning with a period per rfc821.
       
   479         Raises SMTPDataError if there is an unexpected reply to the
       
   480         DATA command; the return value from this method is the final
       
   481         response code received when the all data is sent.
       
   482         """
       
   483         self.putcmd("data")
       
   484         (code,repl)=self.getreply()
       
   485         if self.debuglevel >0 : print>>stderr, "data:", (code,repl)
       
   486         if code != 354:
       
   487             raise SMTPDataError(code,repl)
       
   488         else:
       
   489             q = quotedata(msg)
       
   490             if q[-2:] != CRLF:
       
   491                 q = q + CRLF
       
   492             q = q + "." + CRLF
       
   493             self.send(q)
       
   494             (code,msg)=self.getreply()
       
   495             if self.debuglevel >0 : print>>stderr, "data:", (code,msg)
       
   496             return (code,msg)
       
   497 
       
   498     def verify(self, address):
       
   499         """SMTP 'verify' command -- checks for address validity."""
       
   500         self.putcmd("vrfy", quoteaddr(address))
       
   501         return self.getreply()
       
   502     # a.k.a.
       
   503     vrfy=verify
       
   504 
       
   505     def expn(self, address):
       
   506         """SMTP 'verify' command -- checks for address validity."""
       
   507         self.putcmd("expn", quoteaddr(address))
       
   508         return self.getreply()
       
   509 
       
   510     # some useful methods
       
   511 
       
   512     def login(self, user, password):
       
   513         """Log in on an SMTP server that requires authentication.
       
   514 
       
   515         The arguments are:
       
   516             - user:     The user name to authenticate with.
       
   517             - password: The password for the authentication.
       
   518 
       
   519         If there has been no previous EHLO or HELO command this session, this
       
   520         method tries ESMTP EHLO first.
       
   521 
       
   522         This method will return normally if the authentication was successful.
       
   523 
       
   524         This method may raise the following exceptions:
       
   525 
       
   526          SMTPHeloError            The server didn't reply properly to
       
   527                                   the helo greeting.
       
   528          SMTPAuthenticationError  The server didn't accept the username/
       
   529                                   password combination.
       
   530          SMTPException            No suitable authentication method was
       
   531                                   found.
       
   532         """
       
   533 
       
   534         def encode_cram_md5(challenge, user, password):
       
   535             challenge = base64.decodestring(challenge)
       
   536             response = user + " " + hmac.HMAC(password, challenge).hexdigest()
       
   537             return encode_base64(response, eol="")
       
   538 
       
   539         def encode_plain(user, password):
       
   540             return encode_base64("\0%s\0%s" % (user, password), eol="")
       
   541 
       
   542 
       
   543         AUTH_PLAIN = "PLAIN"
       
   544         AUTH_CRAM_MD5 = "CRAM-MD5"
       
   545         AUTH_LOGIN = "LOGIN"
       
   546 
       
   547         if self.helo_resp is None and self.ehlo_resp is None:
       
   548             if not (200 <= self.ehlo()[0] <= 299):
       
   549                 (code, resp) = self.helo()
       
   550                 if not (200 <= code <= 299):
       
   551                     raise SMTPHeloError(code, resp)
       
   552 
       
   553         if not self.has_extn("auth"):
       
   554             raise SMTPException("SMTP AUTH extension not supported by server.")
       
   555 
       
   556         # Authentication methods the server supports:
       
   557         authlist = self.esmtp_features["auth"].split()
       
   558 
       
   559         # List of authentication methods we support: from preferred to
       
   560         # less preferred methods. Except for the purpose of testing the weaker
       
   561         # ones, we prefer stronger methods like CRAM-MD5:
       
   562         preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN, AUTH_LOGIN]
       
   563 
       
   564         # Determine the authentication method we'll use
       
   565         authmethod = None
       
   566         for method in preferred_auths:
       
   567             if method in authlist:
       
   568                 authmethod = method
       
   569                 break
       
   570 
       
   571         if authmethod == AUTH_CRAM_MD5:
       
   572             (code, resp) = self.docmd("AUTH", AUTH_CRAM_MD5)
       
   573             if code == 503:
       
   574                 # 503 == 'Error: already authenticated'
       
   575                 return (code, resp)
       
   576             (code, resp) = self.docmd(encode_cram_md5(resp, user, password))
       
   577         elif authmethod == AUTH_PLAIN:
       
   578             (code, resp) = self.docmd("AUTH",
       
   579                 AUTH_PLAIN + " " + encode_plain(user, password))
       
   580         elif authmethod == AUTH_LOGIN:
       
   581             (code, resp) = self.docmd("AUTH",
       
   582                 "%s %s" % (AUTH_LOGIN, encode_base64(user, eol="")))
       
   583             if code != 334:
       
   584                 raise SMTPAuthenticationError(code, resp)
       
   585             (code, resp) = self.docmd(encode_base64(password, eol=""))
       
   586         elif authmethod is None:
       
   587             raise SMTPException("No suitable authentication method found.")
       
   588         if code not in (235, 503):
       
   589             # 235 == 'Authentication successful'
       
   590             # 503 == 'Error: already authenticated'
       
   591             raise SMTPAuthenticationError(code, resp)
       
   592         return (code, resp)
       
   593 
       
   594     def starttls(self, keyfile = None, certfile = None):
       
   595         """Puts the connection to the SMTP server into TLS mode.
       
   596 
       
   597         If the server supports TLS, this will encrypt the rest of the SMTP
       
   598         session. If you provide the keyfile and certfile parameters,
       
   599         the identity of the SMTP server and client can be checked. This,
       
   600         however, depends on whether the socket module really checks the
       
   601         certificates.
       
   602         """
       
   603         (resp, reply) = self.docmd("STARTTLS")
       
   604         if resp == 220:
       
   605             sslobj = socket.ssl(self.sock, keyfile, certfile)
       
   606             self.sock = SSLFakeSocket(self.sock, sslobj)
       
   607             self.file = SSLFakeFile(sslobj)
       
   608             # RFC 3207:
       
   609             # The client MUST discard any knowledge obtained from
       
   610             # the server, such as the list of SMTP service extensions,
       
   611             # which was not obtained from the TLS negotiation itself.
       
   612             self.helo_resp = None
       
   613             self.ehlo_resp = None
       
   614             self.esmtp_features = {}
       
   615             self.does_esmtp = 0
       
   616         return (resp, reply)
       
   617 
       
   618     def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
       
   619                  rcpt_options=[]):
       
   620         """This command performs an entire mail transaction.
       
   621 
       
   622         The arguments are:
       
   623             - from_addr    : The address sending this mail.
       
   624             - to_addrs     : A list of addresses to send this mail to.  A bare
       
   625                              string will be treated as a list with 1 address.
       
   626             - msg          : The message to send.
       
   627             - mail_options : List of ESMTP options (such as 8bitmime) for the
       
   628                              mail command.
       
   629             - rcpt_options : List of ESMTP options (such as DSN commands) for
       
   630                              all the rcpt commands.
       
   631 
       
   632         If there has been no previous EHLO or HELO command this session, this
       
   633         method tries ESMTP EHLO first.  If the server does ESMTP, message size
       
   634         and each of the specified options will be passed to it.  If EHLO
       
   635         fails, HELO will be tried and ESMTP options suppressed.
       
   636 
       
   637         This method will return normally if the mail is accepted for at least
       
   638         one recipient.  It returns a dictionary, with one entry for each
       
   639         recipient that was refused.  Each entry contains a tuple of the SMTP
       
   640         error code and the accompanying error message sent by the server.
       
   641 
       
   642         This method may raise the following exceptions:
       
   643 
       
   644          SMTPHeloError          The server didn't reply properly to
       
   645                                 the helo greeting.
       
   646          SMTPRecipientsRefused  The server rejected ALL recipients
       
   647                                 (no mail was sent).
       
   648          SMTPSenderRefused      The server didn't accept the from_addr.
       
   649          SMTPDataError          The server replied with an unexpected
       
   650                                 error code (other than a refusal of
       
   651                                 a recipient).
       
   652 
       
   653         Note: the connection will be open even after an exception is raised.
       
   654 
       
   655         Example:
       
   656 
       
   657          >>> import smtplib
       
   658          >>> s=smtplib.SMTP("localhost")
       
   659          >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
       
   660          >>> msg = '''\\
       
   661          ... From: Me@my.org
       
   662          ... Subject: testin'...
       
   663          ...
       
   664          ... This is a test '''
       
   665          >>> s.sendmail("me@my.org",tolist,msg)
       
   666          { "three@three.org" : ( 550 ,"User unknown" ) }
       
   667          >>> s.quit()
       
   668 
       
   669         In the above example, the message was accepted for delivery to three
       
   670         of the four addresses, and one was rejected, with the error code
       
   671         550.  If all addresses are accepted, then the method will return an
       
   672         empty dictionary.
       
   673 
       
   674         """
       
   675         if self.helo_resp is None and self.ehlo_resp is None:
       
   676             if not (200 <= self.ehlo()[0] <= 299):
       
   677                 (code,resp) = self.helo()
       
   678                 if not (200 <= code <= 299):
       
   679                     raise SMTPHeloError(code, resp)
       
   680         esmtp_opts = []
       
   681         if self.does_esmtp:
       
   682             # Hmmm? what's this? -ddm
       
   683             # self.esmtp_features['7bit']=""
       
   684             if self.has_extn('size'):
       
   685                 esmtp_opts.append("size=%d" % len(msg))
       
   686             for option in mail_options:
       
   687                 esmtp_opts.append(option)
       
   688 
       
   689         (code,resp) = self.mail(from_addr, esmtp_opts)
       
   690         if code != 250:
       
   691             self.rset()
       
   692             raise SMTPSenderRefused(code, resp, from_addr)
       
   693         senderrs={}
       
   694         if isinstance(to_addrs, basestring):
       
   695             to_addrs = [to_addrs]
       
   696         for each in to_addrs:
       
   697             (code,resp)=self.rcpt(each, rcpt_options)
       
   698             if (code != 250) and (code != 251):
       
   699                 senderrs[each]=(code,resp)
       
   700         if len(senderrs)==len(to_addrs):
       
   701             # the server refused all our recipients
       
   702             self.rset()
       
   703             raise SMTPRecipientsRefused(senderrs)
       
   704         (code,resp) = self.data(msg)
       
   705         if code != 250:
       
   706             self.rset()
       
   707             raise SMTPDataError(code, resp)
       
   708         #if we got here then somebody got our mail
       
   709         return senderrs
       
   710 
       
   711 
       
   712     def close(self):
       
   713         """Close the connection to the SMTP server."""
       
   714         if self.file:
       
   715             self.file.close()
       
   716         self.file = None
       
   717         if self.sock:
       
   718             self.sock.close()
       
   719         self.sock = None
       
   720 
       
   721 
       
   722     def quit(self):
       
   723         """Terminate the SMTP session."""
       
   724         self.docmd("quit")
       
   725         self.close()
       
   726 
       
   727 
       
   728 # Test the sendmail method, which tests most of the others.
       
   729 # Note: This always sends to localhost.
       
   730 if __name__ == '__main__':
       
   731     import sys
       
   732 
       
   733     def prompt(prompt):
       
   734         sys.stdout.write(prompt + ": ")
       
   735         return sys.stdin.readline().strip()
       
   736 
       
   737     fromaddr = prompt("From")
       
   738     toaddrs  = prompt("To").split(',')
       
   739     print "Enter message, end with ^D:"
       
   740     msg = ''
       
   741     while 1:
       
   742         line = sys.stdin.readline()
       
   743         if not line:
       
   744             break
       
   745         msg = msg + line
       
   746     print "Message length is %d" % len(msg)
       
   747 
       
   748     server = SMTP('localhost')
       
   749     server.set_debuglevel(1)
       
   750     server.sendmail(fromaddr, toaddrs, msg)
       
   751     server.quit()