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