smtplib.py 26.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 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
  >>> import smtplib
  >>> s=smtplib.SMTP("localhost")
  >>> print s.help()
  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 encode as encode_base64
50
from sys import stderr
51

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

57 58 59
SMTP_PORT = 25
CRLF="\r\n"

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

Tim Peters's avatar
Tim Peters committed
62
# Exception classes used by this module.
63 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
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.
90

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

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

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

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

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


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

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

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

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

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

130 131
class SSLFakeSocket:
    """A fake socket object that really wraps a SSLObject.
Tim Peters's avatar
Tim Peters committed
132

133 134 135 136 137 138 139 140 141 142
    It only supports what is needed in smtplib.
    """
    def __init__(self, realsock, sslobj):
        self.realsock = realsock
        self.sslobj = sslobj

    def send(self, str):
        self.sslobj.write(str)
        return len(str)

143 144
    sendall = send

145 146 147 148 149
    def close(self):
        self.realsock.close()

class SSLFakeFile:
    """A fake file like object that really wraps a SSLObject.
Tim Peters's avatar
Tim Peters committed
150

151 152
    It only supports what is needed in smtplib.
    """
153
    def __init__(self, sslobj):
154 155 156 157 158 159 160 161 162 163 164 165 166
        self.sslobj = sslobj

    def readline(self):
        str = ""
        chr = None
        while chr != "\n":
            chr = self.sslobj.read(1)
            str += chr
        return str

    def close(self):
        pass

167 168 169
def quoteaddr(addr):
    """Quote a subset of the email addresses defined by RFC 821.

170 171
    Should be able to handle anything rfc822.parseaddr can handle.
    """
172
    m = (None, None)
173
    try:
174
        m = email.Utils.parseaddr(addr)[1]
175 176
    except AttributeError:
        pass
177
    if m == (None, None): # Indicates parse failure or AttributeError
178
        # something weird here.. punt -ddm
179
        return "<%s>" % addr
180 181 182
    elif m is None:
        # the sender wants an empty return address
        return "<>"
183 184
    else:
        return "<%s>" % m
185 186 187 188

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

189
    Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
190 191
    Internet CRLF end-of-line.
    """
192
    return re.sub(r'(?m)^\.', '..',
193
        re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data))
194

195

196
class SMTP:
197 198
    """This class manages a connection to an SMTP or ESMTP server.
    SMTP Objects:
Tim Peters's avatar
Tim Peters committed
199 200 201
        SMTP objects have the following attributes:
            helo_resp
                This is the message given by the server in response to the
202
                most recent HELO command.
Tim Peters's avatar
Tim Peters committed
203

204
            ehlo_resp
Tim Peters's avatar
Tim Peters committed
205
                This is the message given by the server in response to the
206 207
                most recent EHLO command. This is usually multiline.

Tim Peters's avatar
Tim Peters committed
208
            does_esmtp
209 210 211
                This is a True value _after you do an EHLO command_, if the
                server supports ESMTP.

Tim Peters's avatar
Tim Peters committed
212
            esmtp_features
213
                This is a dictionary, which, if the server supports ESMTP,
214 215
                will _after you do an EHLO command_, contain the names of the
                SMTP service extensions this server supports, and their
216
                parameters (if any).
217

Tim Peters's avatar
Tim Peters committed
218 219
                Note, all extension names are mapped to lower case in the
                dictionary.
220

221 222 223 224
        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.
        """
225 226 227 228
    debuglevel = 0
    file = None
    helo_resp = None
    ehlo_resp = None
229
    does_esmtp = 0
230

231
    def __init__(self, host = '', port = 0, local_hostname = None):
232 233
        """Initialize a new instance.

234 235
        If specified, `host' is the name of the remote host to which to
        connect.  If specified, `port' specifies the port to which to connect.
236
        By default, smtplib.SMTP_PORT is used.  An SMTPConnectError is raised
237
        if the specified `host' doesn't respond correctly.  If specified,
Tim Peters's avatar
Tim Peters committed
238 239
        `local_hostname` is used as the FQDN of the local host.  By default,
        the local hostname is found using socket.getfqdn().
240 241

        """
242
        self.esmtp_features = {}
243 244 245 246
        if host:
            (code, msg) = self.connect(host, port)
            if code != 220:
                raise SMTPConnectError(code, msg)
247
        if local_hostname is not None:
248
            self.local_hostname = local_hostname
249
        else:
250 251 252 253 254 255 256 257
            # 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
258 259 260 261 262
                addr = '127.0.0.1'
                try:
                    addr = socket.gethostbyname(socket.gethostname())
                except socket.gaierror:
                    pass
263
                self.local_hostname = '[%s]' % addr
Tim Peters's avatar
Tim Peters committed
264

265 266 267
    def set_debuglevel(self, debuglevel):
        """Set the debug output level.

268 269
        A non-false value results in debug messages for connection and for all
        messages sent to and received from the server.
270 271 272 273 274 275

        """
        self.debuglevel = debuglevel

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

277 278 279
        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.
280

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

        """
285
        if not port and (host.find(':') == host.rfind(':')):
286
            i = host.rfind(':')
287 288
            if i >= 0:
                host, port = host[:i], host[i+1:]
289
                try: port = int(port)
290
                except ValueError:
291 292
                    raise socket.error, "nonnumeric port"
        if not port: port = SMTP_PORT
293
        if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
294
        msg = "getaddrinfo returns an empty list"
295
        self.sock = None
296 297 298 299
        for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
            af, socktype, proto, canonname, sa = res
            try:
                self.sock = socket.socket(af, socktype, proto)
300
                if self.debuglevel > 0: print>>stderr, 'connect:', sa
301 302
                self.sock.connect(sa)
            except socket.error, msg:
303
                if self.debuglevel > 0: print>>stderr, 'connect fail:', msg
304 305
                if self.sock:
                    self.sock.close()
306 307 308 309 310
                self.sock = None
                continue
            break
        if not self.sock:
            raise socket.error, msg
311
        (code, msg) = self.getreply()
312
        if self.debuglevel > 0: print>>stderr, "connect:", msg
313
        return (code, msg)
Tim Peters's avatar
Tim Peters committed
314

315 316
    def send(self, str):
        """Send `str' to the server."""
317
        if self.debuglevel > 0: print>>stderr, 'send:', repr(str)
318
        if self.sock:
319
            try:
320
                self.sock.sendall(str)
321
            except socket.error:
322
                self.close()
323
                raise SMTPServerDisconnected('Server not connected')
Guido van Rossum's avatar
Guido van Rossum committed
324
        else:
325
            raise SMTPServerDisconnected('please run connect() first')
Tim Peters's avatar
Tim Peters committed
326

327
    def putcmd(self, cmd, args=""):
328
        """Send a command to the server."""
329 330 331 332
        if args == "":
            str = '%s%s' % (cmd, CRLF)
        else:
            str = '%s %s%s' % (cmd, args, CRLF)
333
        self.send(str)
Tim Peters's avatar
Tim Peters committed
334

335
    def getreply(self):
336
        """Get a reply from the server.
Tim Peters's avatar
Tim Peters committed
337

338
        Returns a tuple consisting of:
339 340 341 342 343 344

          - 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).
345 346

        Raises SMTPServerDisconnected if end-of-file is reached.
347 348
        """
        resp=[]
349 350
        if self.file is None:
            self.file = self.sock.makefile('rb')
351
        while 1:
352
            line = self.file.readline()
353 354 355
            if line == '':
                self.close()
                raise SMTPServerDisconnected("Connection unexpectedly closed")
356
            if self.debuglevel > 0: print>>stderr, 'reply:', repr(line)
357
            resp.append(line[4:].strip())
358
            code=line[:3]
359 360 361
            # Check that the error code is syntactically correct.
            # Don't attempt to read a continuation line if it is broken.
            try:
362
                errcode = int(code)
363 364 365
            except ValueError:
                errcode = -1
                break
366
            # Check if multiline response.
367
            if line[3:4]!="-":
368 369
                break

370
        errmsg = "\n".join(resp)
Tim Peters's avatar
Tim Peters committed
371
        if self.debuglevel > 0:
372
            print>>stderr, 'reply: retcode (%s); Msg: %s' % (errcode,errmsg)
373
        return errcode, errmsg
Tim Peters's avatar
Tim Peters committed
374

375
    def docmd(self, cmd, args=""):
376
        """Send a command, and return its response code."""
377
        self.putcmd(cmd,args)
378
        return self.getreply()
379

380
    # std smtp commands
381
    def helo(self, name=''):
382 383 384 385
        """SMTP 'helo' command.
        Hostname to send for this command defaults to the FQDN of the local
        host.
        """
386
        self.putcmd("helo", name or self.local_hostname)
387 388
        (code,msg)=self.getreply()
        self.helo_resp=msg
389
        return (code,msg)
390

391
    def ehlo(self, name=''):
392 393 394 395
        """ SMTP 'ehlo' command.
        Hostname to send for this command defaults to the FQDN of the local
        host.
        """
396
        self.esmtp_features = {}
397
        self.putcmd("ehlo", name or self.local_hostname)
398
        (code,msg)=self.getreply()
Tim Peters's avatar
Tim Peters committed
399 400
        # According to RFC1869 some (badly written)
        # MTA's will disconnect on an ehlo. Toss an exception if
401 402
        # that happens -ddm
        if code == -1 and len(msg) == 0:
403
            self.close()
404
            raise SMTPServerDisconnected("Server not connected")
405
        self.ehlo_resp=msg
406
        if code != 250:
407
            return (code,msg)
408
        self.does_esmtp=1
409
        #parse the ehlo response -ddm
410
        resp=self.ehlo_resp.split('\n')
411
        del resp[0]
412
        for each in resp:
413 414 415 416 417 418 419 420 421 422 423 424 425
            # 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

426 427 428 429 430
            # 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)
431
            if m:
432 433
                feature=m.group("feature").lower()
                params=m.string[m.end("feature"):].strip()
434 435 436 437 438
                if feature == "auth":
                    self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \
                            + " " + params
                else:
                    self.esmtp_features[feature]=params
439
        return (code,msg)
440

441 442
    def has_extn(self, opt):
        """Does the server support a given SMTP service extension?"""
443
        return opt.lower() in self.esmtp_features
444

445
    def help(self, args=''):
446 447
        """SMTP 'help' command.
        Returns help text from server."""
448
        self.putcmd("help", args)
449
        return self.getreply()[1]
450 451

    def rset(self):
452
        """SMTP 'rset' command -- resets session."""
453
        return self.docmd("rset")
454 455

    def noop(self):
456
        """SMTP 'noop' command -- doesn't do anything :>"""
457
        return self.docmd("noop")
458

459
    def mail(self,sender,options=[]):
460
        """SMTP 'mail' command -- begins mail xfer session."""
461 462
        optionlist = ''
        if options and self.does_esmtp:
463
            optionlist = ' ' + ' '.join(options)
464
        self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender) ,optionlist))
465
        return self.getreply()
466

467
    def rcpt(self,recip,options=[]):
468
        """SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
469 470
        optionlist = ''
        if options and self.does_esmtp:
471
            optionlist = ' ' + ' '.join(options)
472
        self.putcmd("rcpt","TO:%s%s" % (quoteaddr(recip),optionlist))
473
        return self.getreply()
474 475

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

478
        Automatically quotes lines beginning with a period per rfc821.
479 480 481
        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.
482
        """
483 484
        self.putcmd("data")
        (code,repl)=self.getreply()
485
        if self.debuglevel >0 : print>>stderr, "data:", (code,repl)
486
        if code != 354:
487
            raise SMTPDataError(code,repl)
488
        else:
489 490 491 492 493
            q = quotedata(msg)
            if q[-2:] != CRLF:
                q = q + CRLF
            q = q + "." + CRLF
            self.send(q)
494
            (code,msg)=self.getreply()
495
            if self.debuglevel >0 : print>>stderr, "data:", (code,msg)
496
            return (code,msg)
497

498
    def verify(self, address):
499
        """SMTP 'verify' command -- checks for address validity."""
500 501
        self.putcmd("vrfy", quoteaddr(address))
        return self.getreply()
502 503
    # a.k.a.
    vrfy=verify
504 505

    def expn(self, address):
506
        """SMTP 'verify' command -- checks for address validity."""
507 508 509
        self.putcmd("expn", quoteaddr(address))
        return self.getreply()

510
    # some useful methods
511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529

    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.
530
         SMTPException            No suitable authentication method was
531 532 533 534 535 536
                                  found.
        """

        def encode_cram_md5(challenge, user, password):
            challenge = base64.decodestring(challenge)
            response = user + " " + hmac.HMAC(password, challenge).hexdigest()
537
            return encode_base64(response, eol="")
538 539

        def encode_plain(user, password):
540
            return encode_base64("\0%s\0%s" % (user, password), eol="")
Tim Peters's avatar
Tim Peters committed
541

542 543 544

        AUTH_PLAIN = "PLAIN"
        AUTH_CRAM_MD5 = "CRAM-MD5"
545
        AUTH_LOGIN = "LOGIN"
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561

        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)

        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:
562
        preferred_auths = [AUTH_CRAM_MD5, AUTH_PLAIN, AUTH_LOGIN]
563 564 565 566 567 568 569

        # 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
570

571 572 573 574 575 576 577
        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
578
            (code, resp) = self.docmd("AUTH",
579
                AUTH_PLAIN + " " + encode_plain(user, password))
580 581 582 583 584
        elif authmethod == AUTH_LOGIN:
            (code, resp) = self.docmd("AUTH",
                "%s %s" % (AUTH_LOGIN, encode_base64(user, eol="")))
            if code != 334:
                raise SMTPAuthenticationError(code, resp)
585
            (code, resp) = self.docmd(encode_base64(password, eol=""))
586
        elif authmethod is None:
587
            raise SMTPException("No suitable authentication method found.")
588
        if code not in (235, 503):
589 590 591 592 593
            # 235 == 'Authentication successful'
            # 503 == 'Error: already authenticated'
            raise SMTPAuthenticationError(code, resp)
        return (code, resp)

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

597 598 599 600 601 602
        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.
        """
Tim Peters's avatar
Tim Peters committed
603
        (resp, reply) = self.docmd("STARTTLS")
604 605 606 607 608
        if resp == 220:
            sslobj = socket.ssl(self.sock, keyfile, certfile)
            self.sock = SSLFakeSocket(self.sock, sslobj)
            self.file = SSLFakeFile(sslobj)
        return (resp, reply)
Tim Peters's avatar
Tim Peters committed
609

610
    def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
Tim Peters's avatar
Tim Peters committed
611 612
                 rcpt_options=[]):
        """This command performs an entire mail transaction.
613

Tim Peters's avatar
Tim Peters committed
614
        The arguments are:
615 616 617
            - 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
618
            - msg          : The message to send.
619 620 621 622 623 624 625 626 627 628 629
            - 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
630 631 632
        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.
633 634 635 636

        This method may raise the following exceptions:

         SMTPHeloError          The server didn't reply properly to
Tim Peters's avatar
Tim Peters committed
637
                                the helo greeting.
638
         SMTPRecipientsRefused  The server rejected ALL recipients
639 640 641 642 643 644 645
                                (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.
646

647
        Example:
Tim Peters's avatar
Tim Peters committed
648

649 650
         >>> import smtplib
         >>> s=smtplib.SMTP("localhost")
Guido van Rossum's avatar
Guido van Rossum committed
651
         >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
652
         >>> msg = '''\\
653 654 655 656 657 658 659
         ... 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
660

661 662
        In the above example, the message was accepted for delivery to three
        of the four addresses, and one was rejected, with the error code
663
        550.  If all addresses are accepted, then the method will return an
664 665 666
        empty dictionary.

        """
667 668 669 670 671
        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)
672
        esmtp_opts = []
673 674 675 676
        if self.does_esmtp:
            # Hmmm? what's this? -ddm
            # self.esmtp_features['7bit']=""
            if self.has_extn('size'):
677
                esmtp_opts.append("size=%d" % len(msg))
678
            for option in mail_options:
679
                esmtp_opts.append(option)
680

681
        (code,resp) = self.mail(from_addr, esmtp_opts)
682
        if code != 250:
683
            self.rset()
684
            raise SMTPSenderRefused(code, resp, from_addr)
685
        senderrs={}
686
        if isinstance(to_addrs, basestring):
687
            to_addrs = [to_addrs]
688
        for each in to_addrs:
689
            (code,resp)=self.rcpt(each, rcpt_options)
690
            if (code != 250) and (code != 251):
Guido van Rossum's avatar
Guido van Rossum committed
691
                senderrs[each]=(code,resp)
692
        if len(senderrs)==len(to_addrs):
693
            # the server refused all our recipients
694
            self.rset()
695
            raise SMTPRecipientsRefused(senderrs)
696 697
        (code,resp) = self.data(msg)
        if code != 250:
698
            self.rset()
699
            raise SMTPDataError(code, resp)
700
        #if we got here then somebody got our mail
Tim Peters's avatar
Tim Peters committed
701
        return senderrs
702 703 704 705 706 707 708 709 710 711 712 713 714


    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):
715
        """Terminate the SMTP session."""
716 717
        self.docmd("quit")
        self.close()
718

719

720 721 722
# Test the sendmail method, which tests most of the others.
# Note: This always sends to localhost.
if __name__ == '__main__':
723
    import sys
724 725 726

    def prompt(prompt):
        sys.stdout.write(prompt + ": ")
727
        return sys.stdin.readline().strip()
728 729

    fromaddr = prompt("From")
730
    toaddrs  = prompt("To").split(',')
731 732 733 734 735 736 737
    print "Enter message, end with ^D:"
    msg = ''
    while 1:
        line = sys.stdin.readline()
        if not line:
            break
        msg = msg + line
738
    print "Message length is %d" % len(msg)
739 740 741 742 743

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