source: titan/mediathek/localhoster/lib/python2.7/smtplib.py @ 40658

Last change on this file since 40658 was 40658, checked in by obi, 7 years ago

update python stuff

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