smtplib.py 30.1 KB
Newer Older
1 2
#! /usr/bin/env python

3
'''SMTP/ESMTP client class.
4

5 6
This should follow RFC 821 (SMTP), RFC 1869 (ESMTP), RFC 2554 (SMTP
Authentication) and RFC 2487 (Secure SMTP over TLS).
7

8 9 10
Notes:

Please remember, when doing ESMTP, that the names of the SMTP service
11
extensions are NOT the same thing as the option keywords for the RCPT
12 13
and MAIL commands!

14 15
Example:

16 17
  >>> import smtplib
  >>> s=smtplib.SMTP("localhost")
18
  >>> print(s.help())
19 20 21 22 23 24 25 26 27 28 29 30 31 32
  This is Sendmail version 8.8.4
  Topics:
      HELO    EHLO    MAIL    RCPT    DATA
      RSET    NOOP    QUIT    HELP    VRFY
      EXPN    VERB    ETRN    DSN
  For more info use "HELP <topic>".
  To report bugs in the implementation send email to
      sendmail-bugs@sendmail.org.
  For local information send email to Postmaster at your site.
  End of HELP info
  >>> s.putcmd("vrfy","someone@here")
  >>> s.getreply()
  (250, "Somebody OverHere <somebody@here.my.org>")
  >>> s.quit()
33
'''
34

35 36 37 38 39
# Author: The Dragon De Monsyne <dragondm@integral.org>
# ESMTP support, test code and doc fixes added by
#     Eric S. Raymond <esr@thyrsus.com>
# Better RFC 821 compliance (MAIL and RCPT, and CRLF in data)
#     by Carey Evans <c.evans@clear.net.nz>, for picky mail servers.
40
# RFC 2554 (authentication) support by Gerhard Haering <gerhard@bigfoot.de>.
Tim Peters's avatar
Tim Peters committed
41
#
42 43
# This was modified from the Python 1.5 library HTTP lib.

44
import socket
45
import re
46
import email.utils
47 48
import base64
import hmac
49
from email.base64mime import body_encode as encode_base64
50
from sys import stderr
51

52 53
__all__ = ["SMTPException","SMTPServerDisconnected","SMTPResponseException",
           "SMTPSenderRefused","SMTPRecipientsRefused","SMTPDataError",
54
           "SMTPConnectError","SMTPHeloError","SMTPAuthenticationError",
55
           "quoteaddr","quotedata","SMTP"]
56

57
SMTP_PORT = 25
58
SMTP_SSL_PORT = 465
59 60
CRLF="\r\n"

61 62
OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I)

Tim Peters's avatar
Tim Peters committed
63
# Exception classes used by this module.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
class SMTPException(Exception):
    """Base class for all exceptions raised by this module."""

class SMTPServerDisconnected(SMTPException):
    """Not connected to any SMTP server.

    This exception is raised when the server unexpectedly disconnects,
    or when an attempt is made to use the SMTP instance before
    connecting it to a server.
    """

class SMTPResponseException(SMTPException):
    """Base class for all exceptions that include an SMTP error code.

    These exceptions are generated in some instances when the SMTP
    server returns an error code.  The error code is stored in the
    `smtp_code' attribute of the error, and the `smtp_error' attribute
    is set to the error message.
    """

    def __init__(self, code, msg):
        self.smtp_code = code
        self.smtp_error = msg
        self.args = (code, msg)

class SMTPSenderRefused(SMTPResponseException):
    """Sender address refused.
91

92
    In addition to the attributes set by on all SMTPResponseException
93
    exceptions, this sets `sender' to the string that the SMTP refused.
94 95 96 97 98 99 100 101
    """

    def __init__(self, code, msg, sender):
        self.smtp_code = code
        self.smtp_error = msg
        self.sender = sender
        self.args = (code, msg, sender)

102
class SMTPRecipientsRefused(SMTPException):
103
    """All recipient addresses refused.
104

105
    The errors for each recipient are accessible through the attribute
Tim Peters's avatar
Tim Peters committed
106 107
    'recipients', which is a dictionary of exactly the same sort as
    SMTP.sendmail() returns.
108 109 110 111 112 113 114 115 116 117 118
    """

    def __init__(self, recipients):
        self.recipients = recipients
        self.args = ( recipients,)


class SMTPDataError(SMTPResponseException):
    """The SMTP server didn't accept the data."""

class SMTPConnectError(SMTPResponseException):
119
    """Error during connection establishment."""
120 121

class SMTPHeloError(SMTPResponseException):
122
    """The server refused our HELO reply."""
123

124 125 126 127 128 129
class SMTPAuthenticationError(SMTPResponseException):
    """Authentication error.

    Most probably the server didn't accept the username/password
    combination provided.
    """
130

131 132 133
def quoteaddr(addr):
    """Quote a subset of the email addresses defined by RFC 821.

Georg Brandl's avatar
Georg Brandl committed
134
    Should be able to handle anything email.utils.parseaddr can handle.
135
    """
136
    m = (None, None)
137
    try:
138
        m = email.utils.parseaddr(addr)[1]
139 140
    except AttributeError:
        pass
141
    if m == (None, None): # Indicates parse failure or AttributeError
142
        # something weird here.. punt -ddm
143
        return "<%s>" % addr
144 145 146
    elif m is None:
        # the sender wants an empty return address
        return "<>"
147 148
    else:
        return "<%s>" % m
149 150 151 152

def quotedata(data):
    """Quote data for email.

153
    Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
154 155
    Internet CRLF end-of-line.
    """
156
    return re.sub(r'(?m)^\.', '..',
157
        re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data))
158

159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
try:
    import ssl
except ImportError:
    _have_ssl = False
else:
    class SSLFakeFile:
        """A fake file like object that really wraps a SSLObject.

        It only supports what is needed in smtplib.
        """
        def __init__(self, sslobj):
            self.sslobj = sslobj

        def readline(self):
            str = b""
            chr = None
            while chr != b"\n":
                chr = self.sslobj.read(1)
177
                if not chr: break
178 179 180 181 182 183 184 185
                str += chr
            return str

        def close(self):
            pass

    _have_ssl = True

186

187
class SMTP:
188 189
    """This class manages a connection to an SMTP or ESMTP server.
    SMTP Objects:
Tim Peters's avatar
Tim Peters committed
190 191 192
        SMTP objects have the following attributes:
            helo_resp
                This is the message given by the server in response to the
193
                most recent HELO command.
Tim Peters's avatar
Tim Peters committed
194

195
            ehlo_resp
Tim Peters's avatar
Tim Peters committed
196
                This is the message given by the server in response to the
197 198
                most recent EHLO command. This is usually multiline.

Tim Peters's avatar
Tim Peters committed
199
            does_esmtp
200 201 202
                This is a True value _after you do an EHLO command_, if the
                server supports ESMTP.

Tim Peters's avatar
Tim Peters committed
203
            esmtp_features
204
                This is a dictionary, which, if the server supports ESMTP,
205 206
                will _after you do an EHLO command_, contain the names of the
                SMTP service extensions this server supports, and their
207
                parameters (if any).
208

Tim Peters's avatar
Tim Peters committed
209 210
                Note, all extension names are mapped to lower case in the
                dictionary.
211

212 213 214 215
        See each method's docstrings for details.  In general, there is a
        method of the same name to perform each SMTP command.  There is also a
        method called 'sendmail' that will do an entire mail transaction.
        """
216 217 218
    debuglevel = 0
    file = None
    helo_resp = None
219
    ehlo_msg = "ehlo"
220
    ehlo_resp = None
221
    does_esmtp = 0
222

Georg Brandl's avatar
Georg Brandl committed
223 224
    def __init__(self, host='', port=0, local_hostname=None,
                 timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
225 226
        """Initialize a new instance.

227 228
        If specified, `host' is the name of the remote host to which to
        connect.  If specified, `port' specifies the port to which to connect.
229
        By default, smtplib.SMTP_PORT is used.  An SMTPConnectError is raised
230
        if the specified `host' doesn't respond correctly.  If specified,
Tim Peters's avatar
Tim Peters committed
231 232
        `local_hostname` is used as the FQDN of the local host.  By default,
        the local hostname is found using socket.getfqdn().
233 234

        """
235
        self.timeout = timeout
236
        self.esmtp_features = {}
237
        self.default_port = SMTP_PORT
238 239 240 241
        if host:
            (code, msg) = self.connect(host, port)
            if code != 220:
                raise SMTPConnectError(code, msg)
242
        if local_hostname is not None:
243
            self.local_hostname = local_hostname
244
        else:
245 246 247 248 249 250 251 252
            # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and
            # if that can't be calculated, that we should use a domain literal
            # instead (essentially an encoded IP address like [A.B.C.D]).
            fqdn = socket.getfqdn()
            if '.' in fqdn:
                self.local_hostname = fqdn
            else:
                # We can't find an fqdn hostname, so use a domain literal
253 254 255 256 257
                addr = '127.0.0.1'
                try:
                    addr = socket.gethostbyname(socket.gethostname())
                except socket.gaierror:
                    pass
258
                self.local_hostname = '[%s]' % addr
Tim Peters's avatar
Tim Peters committed
259

260 261 262
    def set_debuglevel(self, debuglevel):
        """Set the debug output level.

263 264
        A non-false value results in debug messages for connection and for all
        messages sent to and received from the server.
265 266 267 268

        """
        self.debuglevel = debuglevel

269
    def _get_socket(self, host, port, timeout):
270 271
        # This makes it simpler for SMTP_SSL to use the SMTP connect code
        # and just alter the socket connection bit.
272
        if self.debuglevel > 0: print('connect:', (host, port), file=stderr)
273
        return socket.create_connection((host, port), timeout)
274

275 276
    def connect(self, host='localhost', port = 0):
        """Connect to a host on a given port.
277

278 279 280
        If the hostname ends with a colon (`:') followed by a number, and
        there is no port specified, that suffix will be stripped off and the
        number interpreted as the port number to use.
281

282 283
        Note: This method is automatically invoked by __init__, if a host is
        specified during instantiation.
284 285

        """
286
        if not port and (host.find(':') == host.rfind(':')):
287
            i = host.rfind(':')
288 289
            if i >= 0:
                host, port = host[:i], host[i+1:]
290
                try: port = int(port)
291
                except ValueError:
292
                    raise socket.error("nonnumeric port")
293
        if not port: port = self.default_port
294
        if self.debuglevel > 0: print('connect:', (host, port), file=stderr)
295
        self.sock = self._get_socket(host, port, self.timeout)
296
        (code, msg) = self.getreply()
297
        if self.debuglevel > 0: print("connect:", msg, file=stderr)
298
        return (code, msg)
Tim Peters's avatar
Tim Peters committed
299

300 301 302
    def send(self, s):
        """Send `s' to the server."""
        if self.debuglevel > 0: print('send:', repr(s), file=stderr)
Christian Heimes's avatar
Christian Heimes committed
303
        if hasattr(self, 'sock') and self.sock:
304 305
            if isinstance(s, str):
                s = s.encode("ascii")
306
            try:
307
                self.sock.sendall(s)
308
            except socket.error:
309
                self.close()
310
                raise SMTPServerDisconnected('Server not connected')
Guido van Rossum's avatar
Guido van Rossum committed
311
        else:
312
            raise SMTPServerDisconnected('please run connect() first')
Tim Peters's avatar
Tim Peters committed
313

314
    def putcmd(self, cmd, args=""):
315
        """Send a command to the server."""
316 317 318 319
        if args == "":
            str = '%s%s' % (cmd, CRLF)
        else:
            str = '%s %s%s' % (cmd, args, CRLF)
320
        self.send(str)
Tim Peters's avatar
Tim Peters committed
321

322
    def getreply(self):
323
        """Get a reply from the server.
Tim Peters's avatar
Tim Peters committed
324

325
        Returns a tuple consisting of:
326 327 328 329 330 331

          - server response code (e.g. '250', or such, if all goes well)
            Note: returns -1 if it can't read response code.

          - server response string corresponding to response code (multiline
            responses are converted to a single, multiline string).
332 333

        Raises SMTPServerDisconnected if end-of-file is reached.
334 335
        """
        resp=[]
336 337
        if self.file is None:
            self.file = self.sock.makefile('rb')
338
        while 1:
339 340 341 342
            try:
                line = self.file.readline()
            except socket.error:
                line = ''
343
            if not line:
344 345
                self.close()
                raise SMTPServerDisconnected("Connection unexpectedly closed")
346
            if self.debuglevel > 0: print('reply:', repr(line), file=stderr)
347
            resp.append(line[4:].strip(b' \t\r\n'))
348
            code=line[:3]
349 350 351
            # Check that the error code is syntactically correct.
            # Don't attempt to read a continuation line if it is broken.
            try:
352
                errcode = int(code)
353 354 355
            except ValueError:
                errcode = -1
                break
356
            # Check if multiline response.
357
            if line[3:4] != b"-":
358 359
                break

360
        errmsg = b"\n".join(resp)
Tim Peters's avatar
Tim Peters committed
361
        if self.debuglevel > 0:
362
            print('reply: retcode (%s); Msg: %s' % (errcode,errmsg), file=stderr)
363
        return errcode, errmsg
Tim Peters's avatar
Tim Peters committed
364

365
    def docmd(self, cmd, args=""):
366
        """Send a command, and return its response code."""
367
        self.putcmd(cmd,args)
368
        return self.getreply()
369

370
    # std smtp commands
371
    def helo(self, name=''):
372 373 374 375
        """SMTP 'helo' command.
        Hostname to send for this command defaults to the FQDN of the local
        host.
        """
376
        self.putcmd("helo", name or self.local_hostname)
377 378
        (code,msg)=self.getreply()
        self.helo_resp=msg
379
        return (code,msg)
380

381
    def ehlo(self, name=''):
382 383 384 385
        """ SMTP 'ehlo' command.
        Hostname to send for this command defaults to the FQDN of the local
        host.
        """
386
        self.esmtp_features = {}
387
        self.putcmd(self.ehlo_msg, name or self.local_hostname)
388
        (code,msg)=self.getreply()
Tim Peters's avatar
Tim Peters committed
389 390
        # According to RFC1869 some (badly written)
        # MTA's will disconnect on an ehlo. Toss an exception if
391 392
        # that happens -ddm
        if code == -1 and len(msg) == 0:
393
            self.close()
394
            raise SMTPServerDisconnected("Server not connected")
395
        self.ehlo_resp=msg
396
        if code != 250:
397
            return (code,msg)
398
        self.does_esmtp=1
399
        #parse the ehlo response -ddm
400 401
        assert isinstance(self.ehlo_resp, bytes), repr(self.ehlo_resp)
        resp=self.ehlo_resp.decode("latin-1").split('\n')
402
        del resp[0]
403
        for each in resp:
404 405 406 407 408 409 410 411 412 413 414 415 416
            # To be able to communicate with as many SMTP servers as possible,
            # we have to take the old-style auth advertisement into account,
            # because:
            # 1) Else our SMTP feature parser gets confused.
            # 2) There are some servers that only advertise the auth methods we
            #    support using the old style.
            auth_match = OLDSTYLE_AUTH.match(each)
            if auth_match:
                # This doesn't remove duplicates, but that's no problem
                self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \
                        + " " + auth_match.groups(0)[0]
                continue

417 418 419 420 421
            # RFC 1869 requires a space between ehlo keyword and parameters.
            # It's actually stricter, in that only spaces are allowed between
            # parameters, but were not going to check for that here.  Note
            # that the space isn't present if there are no parameters.
            m=re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?',each)
422
            if m:
423 424
                feature=m.group("feature").lower()
                params=m.string[m.end("feature"):].strip()
425 426 427 428 429
                if feature == "auth":
                    self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \
                            + " " + params
                else:
                    self.esmtp_features[feature]=params
430
        return (code,msg)
431

432 433
    def has_extn(self, opt):
        """Does the server support a given SMTP service extension?"""
434
        return opt.lower() in self.esmtp_features
435

436
    def help(self, args=''):
437 438
        """SMTP 'help' command.
        Returns help text from server."""
439
        self.putcmd("help", args)
440
        return self.getreply()[1]
441 442

    def rset(self):
443
        """SMTP 'rset' command -- resets session."""
444
        return self.docmd("rset")
445 446

    def noop(self):
447
        """SMTP 'noop' command -- doesn't do anything :>"""
448
        return self.docmd("noop")
449

450
    def mail(self,sender,options=[]):
451
        """SMTP 'mail' command -- begins mail xfer session."""
452 453
        optionlist = ''
        if options and self.does_esmtp:
454
            optionlist = ' ' + ' '.join(options)
455
        self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender) ,optionlist))
456
        return self.getreply()
457

458
    def rcpt(self,recip,options=[]):
459
        """SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
460 461
        optionlist = ''
        if options and self.does_esmtp:
462
            optionlist = ' ' + ' '.join(options)
463
        self.putcmd("rcpt","TO:%s%s" % (quoteaddr(recip),optionlist))
464
        return self.getreply()
465 466

    def data(self,msg):
Tim Peters's avatar
Tim Peters committed
467
        """SMTP 'DATA' command -- sends message data to server.
468

469
        Automatically quotes lines beginning with a period per rfc821.
470 471 472
        Raises SMTPDataError if there is an unexpected reply to the
        DATA command; the return value from this method is the final
        response code received when the all data is sent.
473
        """
474 475
        self.putcmd("data")
        (code,repl)=self.getreply()
476
        if self.debuglevel >0 : print("data:", (code,repl), file=stderr)
477
        if code != 354:
478
            raise SMTPDataError(code,repl)
479
        else:
480 481 482 483 484
            q = quotedata(msg)
            if q[-2:] != CRLF:
                q = q + CRLF
            q = q + "." + CRLF
            self.send(q)
485
            (code,msg)=self.getreply()
486
            if self.debuglevel >0 : print("data:", (code,msg), file=stderr)
487
            return (code,msg)
488

489
    def verify(self, address):
490
        """SMTP 'verify' command -- checks for address validity."""
491 492
        self.putcmd("vrfy", quoteaddr(address))
        return self.getreply()
493 494
    # a.k.a.
    vrfy=verify
495 496

    def expn(self, address):
Christian Heimes's avatar
Christian Heimes committed
497
        """SMTP 'expn' command -- expands a mailing list."""
498 499 500
        self.putcmd("expn", quoteaddr(address))
        return self.getreply()

501
    # some useful methods
502

503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519
    def ehlo_or_helo_if_needed(self):
        """Call self.ehlo() and/or self.helo() if needed.

        If there has been no previous EHLO or HELO command this session, this
        method tries ESMTP EHLO first.

        This method may raise the following exceptions:

         SMTPHeloError            The server didn't reply properly to
                                  the helo greeting.
        """
        if self.helo_resp is None and self.ehlo_resp is None:
            if not (200 <= self.ehlo()[0] <= 299):
                (code, resp) = self.helo()
                if not (200 <= code <= 299):
                    raise SMTPHeloError(code, resp)

520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537
    def login(self, user, password):
        """Log in on an SMTP server that requires authentication.

        The arguments are:
            - user:     The user name to authenticate with.
            - password: The password for the authentication.

        If there has been no previous EHLO or HELO command this session, this
        method tries ESMTP EHLO first.

        This method will return normally if the authentication was successful.

        This method may raise the following exceptions:

         SMTPHeloError            The server didn't reply properly to
                                  the helo greeting.
         SMTPAuthenticationError  The server didn't accept the username/
                                  password combination.
538
         SMTPException            No suitable authentication method was
539 540 541 542
                                  found.
        """

        def encode_cram_md5(challenge, user, password):
543
            challenge = base64.decodebytes(challenge)
544 545 546
            response = user + " " + hmac.HMAC(password.encode('ascii'),
                                              challenge).hexdigest()
            return encode_base64(response.encode('ascii'), eol='')
547 548

        def encode_plain(user, password):
549 550
            s = "\0%s\0%s" % (user, password)
            return encode_base64(s.encode('ascii'), eol='')
Tim Peters's avatar
Tim Peters committed
551

552 553 554

        AUTH_PLAIN = "PLAIN"
        AUTH_CRAM_MD5 = "CRAM-MD5"
555
        AUTH_LOGIN = "LOGIN"
556

557
        self.ehlo_or_helo_if_needed()
558 559 560 561 562 563 564 565 566 567

        if not self.has_extn("auth"):
            raise SMTPException("SMTP AUTH extension not supported by server.")

        # Authentication methods the server supports:
        authlist = self.esmtp_features["auth"].split()

        # List of authentication methods we support: from preferred to
        # less preferred methods. Except for the purpose of testing the weaker
        # ones, we prefer stronger methods like CRAM-MD5:
568
        preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN, AUTH_LOGIN]
569 570 571 572 573 574 575

        # Determine the authentication method we'll use
        authmethod = None
        for method in preferred_auths:
            if method in authlist:
                authmethod = method
                break
Tim Peters's avatar
Tim Peters committed
576

577 578 579 580 581 582 583
        if authmethod == AUTH_CRAM_MD5:
            (code, resp) = self.docmd("AUTH", AUTH_CRAM_MD5)
            if code == 503:
                # 503 == 'Error: already authenticated'
                return (code, resp)
            (code, resp) = self.docmd(encode_cram_md5(resp, user, password))
        elif authmethod == AUTH_PLAIN:
Tim Peters's avatar
Tim Peters committed
584
            (code, resp) = self.docmd("AUTH",
585
                AUTH_PLAIN + " " + encode_plain(user, password))
586 587
        elif authmethod == AUTH_LOGIN:
            (code, resp) = self.docmd("AUTH",
588
                "%s %s" % (AUTH_LOGIN, encode_base64(user.encode('ascii'), eol='')))
589 590
            if code != 334:
                raise SMTPAuthenticationError(code, resp)
591
            (code, resp) = self.docmd(encode_base64(password.encode('ascii'), eol=''))
592
        elif authmethod is None:
593
            raise SMTPException("No suitable authentication method found.")
594
        if code not in (235, 503):
595 596 597 598 599
            # 235 == 'Authentication successful'
            # 503 == 'Error: already authenticated'
            raise SMTPAuthenticationError(code, resp)
        return (code, resp)

600 601
    def starttls(self, keyfile = None, certfile = None):
        """Puts the connection to the SMTP server into TLS mode.
Tim Peters's avatar
Tim Peters committed
602

603 604 605
        If there has been no previous EHLO or HELO command this session, this
        method tries ESMTP EHLO first.

606 607 608 609 610
        If the server supports TLS, this will encrypt the rest of the SMTP
        session. If you provide the keyfile and certfile parameters,
        the identity of the SMTP server and client can be checked. This,
        however, depends on whether the socket module really checks the
        certificates.
611 612 613 614 615

        This method may raise the following exceptions:

         SMTPHeloError            The server didn't reply properly to
                                  the helo greeting.
616
        """
617 618 619
        self.ehlo_or_helo_if_needed()
        if not self.has_extn("starttls"):
            raise SMTPException("STARTTLS extension not supported by server.")
Tim Peters's avatar
Tim Peters committed
620
        (resp, reply) = self.docmd("STARTTLS")
621
        if resp == 220:
622 623
            if not _have_ssl:
                raise RuntimeError("No SSL support included in this Python")
624
            self.sock = ssl.wrap_socket(self.sock, keyfile, certfile)
625
            self.file = SSLFakeFile(self.sock)
626 627 628 629 630 631 632 633
            # RFC 3207:
            # The client MUST discard any knowledge obtained from
            # the server, such as the list of SMTP service extensions,
            # which was not obtained from the TLS negotiation itself.
            self.helo_resp = None
            self.ehlo_resp = None
            self.esmtp_features = {}
            self.does_esmtp = 0
634
        return (resp, reply)
Tim Peters's avatar
Tim Peters committed
635

636
    def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
Tim Peters's avatar
Tim Peters committed
637 638
                 rcpt_options=[]):
        """This command performs an entire mail transaction.
639

Tim Peters's avatar
Tim Peters committed
640
        The arguments are:
641 642 643
            - from_addr    : The address sending this mail.
            - to_addrs     : A list of addresses to send this mail to.  A bare
                             string will be treated as a list with 1 address.
Tim Peters's avatar
Tim Peters committed
644
            - msg          : The message to send.
645 646 647 648 649 650 651 652 653 654 655
            - mail_options : List of ESMTP options (such as 8bitmime) for the
                             mail command.
            - rcpt_options : List of ESMTP options (such as DSN commands) for
                             all the rcpt commands.

        If there has been no previous EHLO or HELO command this session, this
        method tries ESMTP EHLO first.  If the server does ESMTP, message size
        and each of the specified options will be passed to it.  If EHLO
        fails, HELO will be tried and ESMTP options suppressed.

        This method will return normally if the mail is accepted for at least
656 657 658
        one recipient.  It returns a dictionary, with one entry for each
        recipient that was refused.  Each entry contains a tuple of the SMTP
        error code and the accompanying error message sent by the server.
659 660 661 662

        This method may raise the following exceptions:

         SMTPHeloError          The server didn't reply properly to
Tim Peters's avatar
Tim Peters committed
663
                                the helo greeting.
664
         SMTPRecipientsRefused  The server rejected ALL recipients
665 666 667 668 669 670 671
                                (no mail was sent).
         SMTPSenderRefused      The server didn't accept the from_addr.
         SMTPDataError          The server replied with an unexpected
                                error code (other than a refusal of
                                a recipient).

        Note: the connection will be open even after an exception is raised.
672

673
        Example:
Tim Peters's avatar
Tim Peters committed
674

675 676
         >>> import smtplib
         >>> s=smtplib.SMTP("localhost")
Guido van Rossum's avatar
Guido van Rossum committed
677
         >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
678
         >>> msg = '''\\
679 680 681 682 683 684 685
         ... From: Me@my.org
         ... Subject: testin'...
         ...
         ... This is a test '''
         >>> s.sendmail("me@my.org",tolist,msg)
         { "three@three.org" : ( 550 ,"User unknown" ) }
         >>> s.quit()
Tim Peters's avatar
Tim Peters committed
686

687 688
        In the above example, the message was accepted for delivery to three
        of the four addresses, and one was rejected, with the error code
689
        550.  If all addresses are accepted, then the method will return an
690 691 692
        empty dictionary.

        """
693
        self.ehlo_or_helo_if_needed()
694
        esmtp_opts = []
695 696 697 698
        if self.does_esmtp:
            # Hmmm? what's this? -ddm
            # self.esmtp_features['7bit']=""
            if self.has_extn('size'):
699
                esmtp_opts.append("size=%d" % len(msg))
700
            for option in mail_options:
701
                esmtp_opts.append(option)
702

703
        (code,resp) = self.mail(from_addr, esmtp_opts)
704
        if code != 250:
705
            self.rset()
706
            raise SMTPSenderRefused(code, resp, from_addr)
707
        senderrs={}
708
        if isinstance(to_addrs, str):
709
            to_addrs = [to_addrs]
710
        for each in to_addrs:
711
            (code,resp)=self.rcpt(each, rcpt_options)
712
            if (code != 250) and (code != 251):
Guido van Rossum's avatar
Guido van Rossum committed
713
                senderrs[each]=(code,resp)
714
        if len(senderrs)==len(to_addrs):
715
            # the server refused all our recipients
716
            self.rset()
717
            raise SMTPRecipientsRefused(senderrs)
718 719
        (code,resp) = self.data(msg)
        if code != 250:
720
            self.rset()
721
            raise SMTPDataError(code, resp)
722
        #if we got here then somebody got our mail
Tim Peters's avatar
Tim Peters committed
723
        return senderrs
724 725 726 727 728 729 730 731 732 733 734 735 736


    def close(self):
        """Close the connection to the SMTP server."""
        if self.file:
            self.file.close()
        self.file = None
        if self.sock:
            self.sock.close()
        self.sock = None


    def quit(self):
737
        """Terminate the SMTP session."""
738
        res = self.docmd("quit")
739
        self.close()
740
        return res
741

742 743 744 745 746 747 748 749 750 751 752
if _have_ssl:

    class SMTP_SSL(SMTP):
        """ This is a subclass derived from SMTP that connects over an SSL encrypted
        socket (to use this class you need a socket module that was compiled with SSL
        support). If host is not specified, '' (the local host) is used. If port is
        omitted, the standard SMTP-over-SSL port (465) is used. keyfile and certfile
        are also optional - they can contain a PEM formatted private key and
        certificate chain file for the SSL connection.
        """
        def __init__(self, host='', port=0, local_hostname=None,
Georg Brandl's avatar
Georg Brandl committed
753 754
                     keyfile=None, certfile=None,
                     timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
755 756 757 758 759 760 761
            self.keyfile = keyfile
            self.certfile = certfile
            SMTP.__init__(self, host, port, local_hostname, timeout)
            self.default_port = SMTP_SSL_PORT

        def _get_socket(self, host, port, timeout):
            if self.debuglevel > 0: print('connect:', (host, port), file=stderr)
762 763 764 765
            new_socket = socket.create_connection((host, port), timeout)
            new_socket = ssl.wrap_socket(new_socket, self.keyfile, self.certfile)
            self.file = SSLFakeFile(new_socket)
            return new_socket
766 767

    __all__.append("SMTP_SSL")
768

769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802
#
# LMTP extension
#
LMTP_PORT = 2003

class LMTP(SMTP):
    """LMTP - Local Mail Transfer Protocol

    The LMTP protocol, which is very similar to ESMTP, is heavily based
    on the standard SMTP client. It's common to use Unix sockets for LMTP,
    so our connect() method must support that as well as a regular
    host:port server. To specify a Unix socket, you must use an absolute
    path as the host, starting with a '/'.

    Authentication is supported, using the regular SMTP mechanism. When
    using a Unix socket, LMTP generally don't support or require any
    authentication, but your mileage might vary."""

    ehlo_msg = "lhlo"

    def __init__(self, host = '', port = LMTP_PORT, local_hostname = None):
        """Initialize a new instance."""
        SMTP.__init__(self, host, port, local_hostname)

    def connect(self, host = 'localhost', port = 0):
        """Connect to the LMTP daemon, on either a Unix or a TCP socket."""
        if host[0] != '/':
            return SMTP.connect(self, host, port)

        # Handle Unix-domain sockets.
        try:
            self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
            self.sock.connect(host)
        except socket.error as msg:
803
            if self.debuglevel > 0: print('connect fail:', host, file=stderr)
804 805 806 807 808
            if self.sock:
                self.sock.close()
            self.sock = None
            raise socket.error(msg)
        (code, msg) = self.getreply()
809
        if self.debuglevel > 0: print('connect:', msg, file=stderr)
810 811 812
        return (code, msg)


813 814 815
# Test the sendmail method, which tests most of the others.
# Note: This always sends to localhost.
if __name__ == '__main__':
816
    import sys
817 818 819

    def prompt(prompt):
        sys.stdout.write(prompt + ": ")
820
        return sys.stdin.readline().strip()
821 822

    fromaddr = prompt("From")
823
    toaddrs  = prompt("To").split(',')
824
    print("Enter message, end with ^D:")
825 826 827 828 829 830
    msg = ''
    while 1:
        line = sys.stdin.readline()
        if not line:
            break
        msg = msg + line
831
    print("Message length is %d" % len(msg))
832 833 834 835 836

    server = SMTP('localhost')
    server.set_debuglevel(1)
    server.sendmail(fromaddr, toaddrs, msg)
    server.quit()