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