1#! /usr/bin/env python3 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 io 46import re 47import email.utils 48import email.message 49import email.generator 50import base64 51import hmac 52import copy 53import datetime 54import sys 55from email.base64mime import body_encode as encode_base64 56 57__all__ = ["SMTPException", "SMTPNotSupportedError", "SMTPServerDisconnected", "SMTPResponseException", 58 "SMTPSenderRefused", "SMTPRecipientsRefused", "SMTPDataError", 59 "SMTPConnectError", "SMTPHeloError", "SMTPAuthenticationError", 60 "quoteaddr", "quotedata", "SMTP"] 61 62SMTP_PORT = 25 63SMTP_SSL_PORT = 465 64CRLF = "\r\n" 65bCRLF = b"\r\n" 66_MAXLINE = 8192 # more than 8 times larger than RFC 821, 4.5.3 67 68OLDSTYLE_AUTH = re.compile(r"auth=(.*)", re.I) 69 70# Exception classes used by this module. 71class SMTPException(OSError): 72 """Base class for all exceptions raised by this module.""" 73 74class SMTPNotSupportedError(SMTPException): 75 """The command or option is not supported by the SMTP server. 76 77 This exception is raised when an attempt is made to run a command or a 78 command with an option which is not supported by the server. 79 """ 80 81class SMTPServerDisconnected(SMTPException): 82 """Not connected to any SMTP server. 83 84 This exception is raised when the server unexpectedly disconnects, 85 or when an attempt is made to use the SMTP instance before 86 connecting it to a server. 87 """ 88 89class SMTPResponseException(SMTPException): 90 """Base class for all exceptions that include an SMTP error code. 91 92 These exceptions are generated in some instances when the SMTP 93 server returns an error code. The error code is stored in the 94 `smtp_code' attribute of the error, and the `smtp_error' attribute 95 is set to the error message. 96 """ 97 98 def __init__(self, code, msg): 99 self.smtp_code = code 100 self.smtp_error = msg 101 self.args = (code, msg) 102 103class SMTPSenderRefused(SMTPResponseException): 104 """Sender address refused. 105 106 In addition to the attributes set by on all SMTPResponseException 107 exceptions, this sets `sender' to the string that the SMTP refused. 108 """ 109 110 def __init__(self, code, msg, sender): 111 self.smtp_code = code 112 self.smtp_error = msg 113 self.sender = sender 114 self.args = (code, msg, sender) 115 116class SMTPRecipientsRefused(SMTPException): 117 """All recipient addresses refused. 118 119 The errors for each recipient are accessible through the attribute 120 'recipients', which is a dictionary of exactly the same sort as 121 SMTP.sendmail() returns. 122 """ 123 124 def __init__(self, recipients): 125 self.recipients = recipients 126 self.args = (recipients,) 127 128 129class SMTPDataError(SMTPResponseException): 130 """The SMTP server didn't accept the data.""" 131 132class SMTPConnectError(SMTPResponseException): 133 """Error during connection establishment.""" 134 135class SMTPHeloError(SMTPResponseException): 136 """The server refused our HELO reply.""" 137 138class SMTPAuthenticationError(SMTPResponseException): 139 """Authentication error. 140 141 Most probably the server didn't accept the username/password 142 combination provided. 143 """ 144 145def quoteaddr(addrstring): 146 """Quote a subset of the email addresses defined by RFC 821. 147 148 Should be able to handle anything email.utils.parseaddr can handle. 149 """ 150 displayname, addr = email.utils.parseaddr(addrstring) 151 if (displayname, addr) == ('', ''): 152 # parseaddr couldn't parse it, use it as is and hope for the best. 153 if addrstring.strip().startswith('<'): 154 return addrstring 155 return "<%s>" % addrstring 156 return "<%s>" % addr 157 158def _addr_only(addrstring): 159 displayname, addr = email.utils.parseaddr(addrstring) 160 if (displayname, addr) == ('', ''): 161 # parseaddr couldn't parse it, so use it as is. 162 return addrstring 163 return addr 164 165# Legacy method kept for backward compatibility. 166def quotedata(data): 167 """Quote data for email. 168 169 Double leading '.', and change Unix newline '\\n', or Mac '\\r' into 170 Internet CRLF end-of-line. 171 """ 172 return re.sub(r'(?m)^\.', '..', 173 re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data)) 174 175def _quote_periods(bindata): 176 return re.sub(br'(?m)^\.', b'..', bindata) 177 178def _fix_eols(data): 179 return re.sub(r'(?:\r\n|\n|\r(?!\n))', CRLF, data) 180 181try: 182 import ssl 183except ImportError: 184 _have_ssl = False 185else: 186 _have_ssl = True 187 188 189class SMTP: 190 """This class manages a connection to an SMTP or ESMTP server. 191 SMTP Objects: 192 SMTP objects have the following attributes: 193 helo_resp 194 This is the message given by the server in response to the 195 most recent HELO command. 196 197 ehlo_resp 198 This is the message given by the server in response to the 199 most recent EHLO command. This is usually multiline. 200 201 does_esmtp 202 This is a True value _after you do an EHLO command_, if the 203 server supports ESMTP. 204 205 esmtp_features 206 This is a dictionary, which, if the server supports ESMTP, 207 will _after you do an EHLO command_, contain the names of the 208 SMTP service extensions this server supports, and their 209 parameters (if any). 210 211 Note, all extension names are mapped to lower case in the 212 dictionary. 213 214 See each method's docstrings for details. In general, there is a 215 method of the same name to perform each SMTP command. There is also a 216 method called 'sendmail' that will do an entire mail transaction. 217 """ 218 debuglevel = 0 219 220 sock = None 221 file = None 222 helo_resp = None 223 ehlo_msg = "ehlo" 224 ehlo_resp = None 225 does_esmtp = 0 226 default_port = SMTP_PORT 227 228 def __init__(self, host='', port=0, local_hostname=None, 229 timeout=socket._GLOBAL_DEFAULT_TIMEOUT, 230 source_address=None): 231 """Initialize a new instance. 232 233 If specified, `host' is the name of the remote host to which to 234 connect. If specified, `port' specifies the port to which to connect. 235 By default, smtplib.SMTP_PORT is used. If a host is specified the 236 connect method is called, and if it returns anything other than a 237 success code an SMTPConnectError is raised. If specified, 238 `local_hostname` is used as the FQDN of the local host in the HELO/EHLO 239 command. Otherwise, the local hostname is found using 240 socket.getfqdn(). The `source_address` parameter takes a 2-tuple (host, 241 port) for the socket to bind to as its source address before 242 connecting. If the host is '' and port is 0, the OS default behavior 243 will be used. 244 245 """ 246 self._host = host 247 self.timeout = timeout 248 self.esmtp_features = {} 249 self.command_encoding = 'ascii' 250 self.source_address = source_address 251 252 if host: 253 (code, msg) = self.connect(host, port) 254 if code != 220: 255 self.close() 256 raise SMTPConnectError(code, msg) 257 if local_hostname is not None: 258 self.local_hostname = local_hostname 259 else: 260 # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and 261 # if that can't be calculated, that we should use a domain literal 262 # instead (essentially an encoded IP address like [A.B.C.D]). 263 fqdn = socket.getfqdn() 264 if '.' in fqdn: 265 self.local_hostname = fqdn 266 else: 267 # We can't find an fqdn hostname, so use a domain literal 268 addr = '127.0.0.1' 269 try: 270 addr = socket.gethostbyname(socket.gethostname()) 271 except socket.gaierror: 272 pass 273 self.local_hostname = '[%s]' % addr 274 275 def __enter__(self): 276 return self 277 278 def __exit__(self, *args): 279 try: 280 code, message = self.docmd("QUIT") 281 if code != 221: 282 raise SMTPResponseException(code, message) 283 except SMTPServerDisconnected: 284 pass 285 finally: 286 self.close() 287 288 def set_debuglevel(self, debuglevel): 289 """Set the debug output level. 290 291 A non-false value results in debug messages for connection and for all 292 messages sent to and received from the server. 293 294 """ 295 self.debuglevel = debuglevel 296 297 def _print_debug(self, *args): 298 if self.debuglevel > 1: 299 print(datetime.datetime.now().time(), *args, file=sys.stderr) 300 else: 301 print(*args, file=sys.stderr) 302 303 def _get_socket(self, host, port, timeout): 304 # This makes it simpler for SMTP_SSL to use the SMTP connect code 305 # and just alter the socket connection bit. 306 if timeout is not None and not timeout: 307 raise ValueError('Non-blocking socket (timeout=0) is not supported') 308 if self.debuglevel > 0: 309 self._print_debug('connect: to', (host, port), self.source_address) 310 return socket.create_connection((host, port), timeout, 311 self.source_address) 312 313 def connect(self, host='localhost', port=0, source_address=None): 314 """Connect to a host on a given port. 315 316 If the hostname ends with a colon (`:') followed by a number, and 317 there is no port specified, that suffix will be stripped off and the 318 number interpreted as the port number to use. 319 320 Note: This method is automatically invoked by __init__, if a host is 321 specified during instantiation. 322 323 """ 324 325 if source_address: 326 self.source_address = source_address 327 328 if not port and (host.find(':') == host.rfind(':')): 329 i = host.rfind(':') 330 if i >= 0: 331 host, port = host[:i], host[i + 1:] 332 try: 333 port = int(port) 334 except ValueError: 335 raise OSError("nonnumeric port") 336 if not port: 337 port = self.default_port 338 sys.audit("smtplib.connect", self, host, port) 339 self.sock = self._get_socket(host, port, self.timeout) 340 self.file = None 341 (code, msg) = self.getreply() 342 if self.debuglevel > 0: 343 self._print_debug('connect:', repr(msg)) 344 return (code, msg) 345 346 def send(self, s): 347 """Send `s' to the server.""" 348 if self.debuglevel > 0: 349 self._print_debug('send:', repr(s)) 350 if self.sock: 351 if isinstance(s, str): 352 # send is used by the 'data' command, where command_encoding 353 # should not be used, but 'data' needs to convert the string to 354 # binary itself anyway, so that's not a problem. 355 s = s.encode(self.command_encoding) 356 sys.audit("smtplib.send", self, s) 357 try: 358 self.sock.sendall(s) 359 except OSError: 360 self.close() 361 raise SMTPServerDisconnected('Server not connected') 362 else: 363 raise SMTPServerDisconnected('please run connect() first') 364 365 def putcmd(self, cmd, args=""): 366 """Send a command to the server.""" 367 if args == "": 368 str = '%s%s' % (cmd, CRLF) 369 else: 370 str = '%s %s%s' % (cmd, args, CRLF) 371 self.send(str) 372 373 def getreply(self): 374 """Get a reply from the server. 375 376 Returns a tuple consisting of: 377 378 - server response code (e.g. '250', or such, if all goes well) 379 Note: returns -1 if it can't read response code. 380 381 - server response string corresponding to response code (multiline 382 responses are converted to a single, multiline string). 383 384 Raises SMTPServerDisconnected if end-of-file is reached. 385 """ 386 resp = [] 387 if self.file is None: 388 self.file = self.sock.makefile('rb') 389 while 1: 390 try: 391 line = self.file.readline(_MAXLINE + 1) 392 except OSError as e: 393 self.close() 394 raise SMTPServerDisconnected("Connection unexpectedly closed: " 395 + str(e)) 396 if not line: 397 self.close() 398 raise SMTPServerDisconnected("Connection unexpectedly closed") 399 if self.debuglevel > 0: 400 self._print_debug('reply:', repr(line)) 401 if len(line) > _MAXLINE: 402 self.close() 403 raise SMTPResponseException(500, "Line too long.") 404 resp.append(line[4:].strip(b' \t\r\n')) 405 code = line[:3] 406 # Check that the error code is syntactically correct. 407 # Don't attempt to read a continuation line if it is broken. 408 try: 409 errcode = int(code) 410 except ValueError: 411 errcode = -1 412 break 413 # Check if multiline response. 414 if line[3:4] != b"-": 415 break 416 417 errmsg = b"\n".join(resp) 418 if self.debuglevel > 0: 419 self._print_debug('reply: retcode (%s); Msg: %a' % (errcode, errmsg)) 420 return errcode, errmsg 421 422 def docmd(self, cmd, args=""): 423 """Send a command, and return its response code.""" 424 self.putcmd(cmd, args) 425 return self.getreply() 426 427 # std smtp commands 428 def helo(self, name=''): 429 """SMTP 'helo' command. 430 Hostname to send for this command defaults to the FQDN of the local 431 host. 432 """ 433 self.putcmd("helo", name or self.local_hostname) 434 (code, msg) = self.getreply() 435 self.helo_resp = msg 436 return (code, msg) 437 438 def ehlo(self, name=''): 439 """ SMTP 'ehlo' command. 440 Hostname to send for this command defaults to the FQDN of the local 441 host. 442 """ 443 self.esmtp_features = {} 444 self.putcmd(self.ehlo_msg, name or self.local_hostname) 445 (code, msg) = self.getreply() 446 # According to RFC1869 some (badly written) 447 # MTA's will disconnect on an ehlo. Toss an exception if 448 # that happens -ddm 449 if code == -1 and len(msg) == 0: 450 self.close() 451 raise SMTPServerDisconnected("Server not connected") 452 self.ehlo_resp = msg 453 if code != 250: 454 return (code, msg) 455 self.does_esmtp = 1 456 #parse the ehlo response -ddm 457 assert isinstance(self.ehlo_resp, bytes), repr(self.ehlo_resp) 458 resp = self.ehlo_resp.decode("latin-1").split('\n') 459 del resp[0] 460 for each in resp: 461 # To be able to communicate with as many SMTP servers as possible, 462 # we have to take the old-style auth advertisement into account, 463 # because: 464 # 1) Else our SMTP feature parser gets confused. 465 # 2) There are some servers that only advertise the auth methods we 466 # support using the old style. 467 auth_match = OLDSTYLE_AUTH.match(each) 468 if auth_match: 469 # This doesn't remove duplicates, but that's no problem 470 self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \ 471 + " " + auth_match.groups(0)[0] 472 continue 473 474 # RFC 1869 requires a space between ehlo keyword and parameters. 475 # It's actually stricter, in that only spaces are allowed between 476 # parameters, but were not going to check for that here. Note 477 # that the space isn't present if there are no parameters. 478 m = re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?', each) 479 if m: 480 feature = m.group("feature").lower() 481 params = m.string[m.end("feature"):].strip() 482 if feature == "auth": 483 self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \ 484 + " " + params 485 else: 486 self.esmtp_features[feature] = params 487 return (code, msg) 488 489 def has_extn(self, opt): 490 """Does the server support a given SMTP service extension?""" 491 return opt.lower() in self.esmtp_features 492 493 def help(self, args=''): 494 """SMTP 'help' command. 495 Returns help text from server.""" 496 self.putcmd("help", args) 497 return self.getreply()[1] 498 499 def rset(self): 500 """SMTP 'rset' command -- resets session.""" 501 self.command_encoding = 'ascii' 502 return self.docmd("rset") 503 504 def _rset(self): 505 """Internal 'rset' command which ignores any SMTPServerDisconnected error. 506 507 Used internally in the library, since the server disconnected error 508 should appear to the application when the *next* command is issued, if 509 we are doing an internal "safety" reset. 510 """ 511 try: 512 self.rset() 513 except SMTPServerDisconnected: 514 pass 515 516 def noop(self): 517 """SMTP 'noop' command -- doesn't do anything :>""" 518 return self.docmd("noop") 519 520 def mail(self, sender, options=()): 521 """SMTP 'mail' command -- begins mail xfer session. 522 523 This method may raise the following exceptions: 524 525 SMTPNotSupportedError The options parameter includes 'SMTPUTF8' 526 but the SMTPUTF8 extension is not supported by 527 the server. 528 """ 529 optionlist = '' 530 if options and self.does_esmtp: 531 if any(x.lower()=='smtputf8' for x in options): 532 if self.has_extn('smtputf8'): 533 self.command_encoding = 'utf-8' 534 else: 535 raise SMTPNotSupportedError( 536 'SMTPUTF8 not supported by server') 537 optionlist = ' ' + ' '.join(options) 538 self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist)) 539 return self.getreply() 540 541 def rcpt(self, recip, options=()): 542 """SMTP 'rcpt' command -- indicates 1 recipient for this mail.""" 543 optionlist = '' 544 if options and self.does_esmtp: 545 optionlist = ' ' + ' '.join(options) 546 self.putcmd("rcpt", "TO:%s%s" % (quoteaddr(recip), optionlist)) 547 return self.getreply() 548 549 def data(self, msg): 550 """SMTP 'DATA' command -- sends message data to server. 551 552 Automatically quotes lines beginning with a period per rfc821. 553 Raises SMTPDataError if there is an unexpected reply to the 554 DATA command; the return value from this method is the final 555 response code received when the all data is sent. If msg 556 is a string, lone '\\r' and '\\n' characters are converted to 557 '\\r\\n' characters. If msg is bytes, it is transmitted as is. 558 """ 559 self.putcmd("data") 560 (code, repl) = self.getreply() 561 if self.debuglevel > 0: 562 self._print_debug('data:', (code, repl)) 563 if code != 354: 564 raise SMTPDataError(code, repl) 565 else: 566 if isinstance(msg, str): 567 msg = _fix_eols(msg).encode('ascii') 568 q = _quote_periods(msg) 569 if q[-2:] != bCRLF: 570 q = q + bCRLF 571 q = q + b"." + bCRLF 572 self.send(q) 573 (code, msg) = self.getreply() 574 if self.debuglevel > 0: 575 self._print_debug('data:', (code, msg)) 576 return (code, msg) 577 578 def verify(self, address): 579 """SMTP 'verify' command -- checks for address validity.""" 580 self.putcmd("vrfy", _addr_only(address)) 581 return self.getreply() 582 # a.k.a. 583 vrfy = verify 584 585 def expn(self, address): 586 """SMTP 'expn' command -- expands a mailing list.""" 587 self.putcmd("expn", _addr_only(address)) 588 return self.getreply() 589 590 # some useful methods 591 592 def ehlo_or_helo_if_needed(self): 593 """Call self.ehlo() and/or self.helo() if needed. 594 595 If there has been no previous EHLO or HELO command this session, this 596 method tries ESMTP EHLO first. 597 598 This method may raise the following exceptions: 599 600 SMTPHeloError The server didn't reply properly to 601 the helo greeting. 602 """ 603 if self.helo_resp is None and self.ehlo_resp is None: 604 if not (200 <= self.ehlo()[0] <= 299): 605 (code, resp) = self.helo() 606 if not (200 <= code <= 299): 607 raise SMTPHeloError(code, resp) 608 609 def auth(self, mechanism, authobject, *, initial_response_ok=True): 610 """Authentication command - requires response processing. 611 612 'mechanism' specifies which authentication mechanism is to 613 be used - the valid values are those listed in the 'auth' 614 element of 'esmtp_features'. 615 616 'authobject' must be a callable object taking a single argument: 617 618 data = authobject(challenge) 619 620 It will be called to process the server's challenge response; the 621 challenge argument it is passed will be a bytes. It should return 622 an ASCII string that will be base64 encoded and sent to the server. 623 624 Keyword arguments: 625 - initial_response_ok: Allow sending the RFC 4954 initial-response 626 to the AUTH command, if the authentication methods supports it. 627 """ 628 # RFC 4954 allows auth methods to provide an initial response. Not all 629 # methods support it. By definition, if they return something other 630 # than None when challenge is None, then they do. See issue #15014. 631 mechanism = mechanism.upper() 632 initial_response = (authobject() if initial_response_ok else None) 633 if initial_response is not None: 634 response = encode_base64(initial_response.encode('ascii'), eol='') 635 (code, resp) = self.docmd("AUTH", mechanism + " " + response) 636 else: 637 (code, resp) = self.docmd("AUTH", mechanism) 638 # If server responds with a challenge, send the response. 639 if code == 334: 640 challenge = base64.decodebytes(resp) 641 response = encode_base64( 642 authobject(challenge).encode('ascii'), eol='') 643 (code, resp) = self.docmd(response) 644 if code in (235, 503): 645 return (code, resp) 646 raise SMTPAuthenticationError(code, resp) 647 648 def auth_cram_md5(self, challenge=None): 649 """ Authobject to use with CRAM-MD5 authentication. Requires self.user 650 and self.password to be set.""" 651 # CRAM-MD5 does not support initial-response. 652 if challenge is None: 653 return None 654 return self.user + " " + hmac.HMAC( 655 self.password.encode('ascii'), challenge, 'md5').hexdigest() 656 657 def auth_plain(self, challenge=None): 658 """ Authobject to use with PLAIN authentication. Requires self.user and 659 self.password to be set.""" 660 return "\0%s\0%s" % (self.user, self.password) 661 662 def auth_login(self, challenge=None): 663 """ Authobject to use with LOGIN authentication. Requires self.user and 664 self.password to be set.""" 665 if challenge is None: 666 return self.user 667 else: 668 return self.password 669 670 def login(self, user, password, *, initial_response_ok=True): 671 """Log in on an SMTP server that requires authentication. 672 673 The arguments are: 674 - user: The user name to authenticate with. 675 - password: The password for the authentication. 676 677 Keyword arguments: 678 - initial_response_ok: Allow sending the RFC 4954 initial-response 679 to the AUTH command, if the authentication methods supports it. 680 681 If there has been no previous EHLO or HELO command this session, this 682 method tries ESMTP EHLO first. 683 684 This method will return normally if the authentication was successful. 685 686 This method may raise the following exceptions: 687 688 SMTPHeloError The server didn't reply properly to 689 the helo greeting. 690 SMTPAuthenticationError The server didn't accept the username/ 691 password combination. 692 SMTPNotSupportedError The AUTH command is not supported by the 693 server. 694 SMTPException No suitable authentication method was 695 found. 696 """ 697 698 self.ehlo_or_helo_if_needed() 699 if not self.has_extn("auth"): 700 raise SMTPNotSupportedError( 701 "SMTP AUTH extension not supported by server.") 702 703 # Authentication methods the server claims to support 704 advertised_authlist = self.esmtp_features["auth"].split() 705 706 # Authentication methods we can handle in our preferred order: 707 preferred_auths = ['CRAM-MD5', 'PLAIN', 'LOGIN'] 708 709 # We try the supported authentications in our preferred order, if 710 # the server supports them. 711 authlist = [auth for auth in preferred_auths 712 if auth in advertised_authlist] 713 if not authlist: 714 raise SMTPException("No suitable authentication method found.") 715 716 # Some servers advertise authentication methods they don't really 717 # support, so if authentication fails, we continue until we've tried 718 # all methods. 719 self.user, self.password = user, password 720 for authmethod in authlist: 721 method_name = 'auth_' + authmethod.lower().replace('-', '_') 722 try: 723 (code, resp) = self.auth( 724 authmethod, getattr(self, method_name), 725 initial_response_ok=initial_response_ok) 726 # 235 == 'Authentication successful' 727 # 503 == 'Error: already authenticated' 728 if code in (235, 503): 729 return (code, resp) 730 except SMTPAuthenticationError as e: 731 last_exception = e 732 733 # We could not login successfully. Return result of last attempt. 734 raise last_exception 735 736 def starttls(self, keyfile=None, certfile=None, context=None): 737 """Puts the connection to the SMTP server into TLS mode. 738 739 If there has been no previous EHLO or HELO command this session, this 740 method tries ESMTP EHLO first. 741 742 If the server supports TLS, this will encrypt the rest of the SMTP 743 session. If you provide the keyfile and certfile parameters, 744 the identity of the SMTP server and client can be checked. This, 745 however, depends on whether the socket module really checks the 746 certificates. 747 748 This method may raise the following exceptions: 749 750 SMTPHeloError The server didn't reply properly to 751 the helo greeting. 752 """ 753 self.ehlo_or_helo_if_needed() 754 if not self.has_extn("starttls"): 755 raise SMTPNotSupportedError( 756 "STARTTLS extension not supported by server.") 757 (resp, reply) = self.docmd("STARTTLS") 758 if resp == 220: 759 if not _have_ssl: 760 raise RuntimeError("No SSL support included in this Python") 761 if context is not None and keyfile is not None: 762 raise ValueError("context and keyfile arguments are mutually " 763 "exclusive") 764 if context is not None and certfile is not None: 765 raise ValueError("context and certfile arguments are mutually " 766 "exclusive") 767 if keyfile is not None or certfile is not None: 768 import warnings 769 warnings.warn("keyfile and certfile are deprecated, use a " 770 "custom context instead", DeprecationWarning, 2) 771 if context is None: 772 context = ssl._create_stdlib_context(certfile=certfile, 773 keyfile=keyfile) 774 self.sock = context.wrap_socket(self.sock, 775 server_hostname=self._host) 776 self.file = None 777 # RFC 3207: 778 # The client MUST discard any knowledge obtained from 779 # the server, such as the list of SMTP service extensions, 780 # which was not obtained from the TLS negotiation itself. 781 self.helo_resp = None 782 self.ehlo_resp = None 783 self.esmtp_features = {} 784 self.does_esmtp = 0 785 else: 786 # RFC 3207: 787 # 501 Syntax error (no parameters allowed) 788 # 454 TLS not available due to temporary reason 789 raise SMTPResponseException(resp, reply) 790 return (resp, reply) 791 792 def sendmail(self, from_addr, to_addrs, msg, mail_options=(), 793 rcpt_options=()): 794 """This command performs an entire mail transaction. 795 796 The arguments are: 797 - from_addr : The address sending this mail. 798 - to_addrs : A list of addresses to send this mail to. A bare 799 string will be treated as a list with 1 address. 800 - msg : The message to send. 801 - mail_options : List of ESMTP options (such as 8bitmime) for the 802 mail command. 803 - rcpt_options : List of ESMTP options (such as DSN commands) for 804 all the rcpt commands. 805 806 msg may be a string containing characters in the ASCII range, or a byte 807 string. A string is encoded to bytes using the ascii codec, and lone 808 \\r and \\n characters are converted to \\r\\n characters. 809 810 If there has been no previous EHLO or HELO command this session, this 811 method tries ESMTP EHLO first. If the server does ESMTP, message size 812 and each of the specified options will be passed to it. If EHLO 813 fails, HELO will be tried and ESMTP options suppressed. 814 815 This method will return normally if the mail is accepted for at least 816 one recipient. It returns a dictionary, with one entry for each 817 recipient that was refused. Each entry contains a tuple of the SMTP 818 error code and the accompanying error message sent by the server. 819 820 This method may raise the following exceptions: 821 822 SMTPHeloError The server didn't reply properly to 823 the helo greeting. 824 SMTPRecipientsRefused The server rejected ALL recipients 825 (no mail was sent). 826 SMTPSenderRefused The server didn't accept the from_addr. 827 SMTPDataError The server replied with an unexpected 828 error code (other than a refusal of 829 a recipient). 830 SMTPNotSupportedError The mail_options parameter includes 'SMTPUTF8' 831 but the SMTPUTF8 extension is not supported by 832 the server. 833 834 Note: the connection will be open even after an exception is raised. 835 836 Example: 837 838 >>> import smtplib 839 >>> s=smtplib.SMTP("localhost") 840 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"] 841 >>> msg = '''\\ 842 ... From: Me@my.org 843 ... Subject: testin'... 844 ... 845 ... This is a test ''' 846 >>> s.sendmail("me@my.org",tolist,msg) 847 { "three@three.org" : ( 550 ,"User unknown" ) } 848 >>> s.quit() 849 850 In the above example, the message was accepted for delivery to three 851 of the four addresses, and one was rejected, with the error code 852 550. If all addresses are accepted, then the method will return an 853 empty dictionary. 854 855 """ 856 self.ehlo_or_helo_if_needed() 857 esmtp_opts = [] 858 if isinstance(msg, str): 859 msg = _fix_eols(msg).encode('ascii') 860 if self.does_esmtp: 861 if self.has_extn('size'): 862 esmtp_opts.append("size=%d" % len(msg)) 863 for option in mail_options: 864 esmtp_opts.append(option) 865 (code, resp) = self.mail(from_addr, esmtp_opts) 866 if code != 250: 867 if code == 421: 868 self.close() 869 else: 870 self._rset() 871 raise SMTPSenderRefused(code, resp, from_addr) 872 senderrs = {} 873 if isinstance(to_addrs, str): 874 to_addrs = [to_addrs] 875 for each in to_addrs: 876 (code, resp) = self.rcpt(each, rcpt_options) 877 if (code != 250) and (code != 251): 878 senderrs[each] = (code, resp) 879 if code == 421: 880 self.close() 881 raise SMTPRecipientsRefused(senderrs) 882 if len(senderrs) == len(to_addrs): 883 # the server refused all our recipients 884 self._rset() 885 raise SMTPRecipientsRefused(senderrs) 886 (code, resp) = self.data(msg) 887 if code != 250: 888 if code == 421: 889 self.close() 890 else: 891 self._rset() 892 raise SMTPDataError(code, resp) 893 #if we got here then somebody got our mail 894 return senderrs 895 896 def send_message(self, msg, from_addr=None, to_addrs=None, 897 mail_options=(), rcpt_options=()): 898 """Converts message to a bytestring and passes it to sendmail. 899 900 The arguments are as for sendmail, except that msg is an 901 email.message.Message object. If from_addr is None or to_addrs is 902 None, these arguments are taken from the headers of the Message as 903 described in RFC 2822 (a ValueError is raised if there is more than 904 one set of 'Resent-' headers). Regardless of the values of from_addr and 905 to_addr, any Bcc field (or Resent-Bcc field, when the Message is a 906 resent) of the Message object won't be transmitted. The Message 907 object is then serialized using email.generator.BytesGenerator and 908 sendmail is called to transmit the message. If the sender or any of 909 the recipient addresses contain non-ASCII and the server advertises the 910 SMTPUTF8 capability, the policy is cloned with utf8 set to True for the 911 serialization, and SMTPUTF8 and BODY=8BITMIME are asserted on the send. 912 If the server does not support SMTPUTF8, an SMTPNotSupported error is 913 raised. Otherwise the generator is called without modifying the 914 policy. 915 916 """ 917 # 'Resent-Date' is a mandatory field if the Message is resent (RFC 2822 918 # Section 3.6.6). In such a case, we use the 'Resent-*' fields. However, 919 # if there is more than one 'Resent-' block there's no way to 920 # unambiguously determine which one is the most recent in all cases, 921 # so rather than guess we raise a ValueError in that case. 922 # 923 # TODO implement heuristics to guess the correct Resent-* block with an 924 # option allowing the user to enable the heuristics. (It should be 925 # possible to guess correctly almost all of the time.) 926 927 self.ehlo_or_helo_if_needed() 928 resent = msg.get_all('Resent-Date') 929 if resent is None: 930 header_prefix = '' 931 elif len(resent) == 1: 932 header_prefix = 'Resent-' 933 else: 934 raise ValueError("message has more than one 'Resent-' header block") 935 if from_addr is None: 936 # Prefer the sender field per RFC 2822:3.6.2. 937 from_addr = (msg[header_prefix + 'Sender'] 938 if (header_prefix + 'Sender') in msg 939 else msg[header_prefix + 'From']) 940 from_addr = email.utils.getaddresses([from_addr])[0][1] 941 if to_addrs is None: 942 addr_fields = [f for f in (msg[header_prefix + 'To'], 943 msg[header_prefix + 'Bcc'], 944 msg[header_prefix + 'Cc']) 945 if f is not None] 946 to_addrs = [a[1] for a in email.utils.getaddresses(addr_fields)] 947 # Make a local copy so we can delete the bcc headers. 948 msg_copy = copy.copy(msg) 949 del msg_copy['Bcc'] 950 del msg_copy['Resent-Bcc'] 951 international = False 952 try: 953 ''.join([from_addr, *to_addrs]).encode('ascii') 954 except UnicodeEncodeError: 955 if not self.has_extn('smtputf8'): 956 raise SMTPNotSupportedError( 957 "One or more source or delivery addresses require" 958 " internationalized email support, but the server" 959 " does not advertise the required SMTPUTF8 capability") 960 international = True 961 with io.BytesIO() as bytesmsg: 962 if international: 963 g = email.generator.BytesGenerator( 964 bytesmsg, policy=msg.policy.clone(utf8=True)) 965 mail_options = (*mail_options, 'SMTPUTF8', 'BODY=8BITMIME') 966 else: 967 g = email.generator.BytesGenerator(bytesmsg) 968 g.flatten(msg_copy, linesep='\r\n') 969 flatmsg = bytesmsg.getvalue() 970 return self.sendmail(from_addr, to_addrs, flatmsg, mail_options, 971 rcpt_options) 972 973 def close(self): 974 """Close the connection to the SMTP server.""" 975 try: 976 file = self.file 977 self.file = None 978 if file: 979 file.close() 980 finally: 981 sock = self.sock 982 self.sock = None 983 if sock: 984 sock.close() 985 986 def quit(self): 987 """Terminate the SMTP session.""" 988 res = self.docmd("quit") 989 # A new EHLO is required after reconnecting with connect() 990 self.ehlo_resp = self.helo_resp = None 991 self.esmtp_features = {} 992 self.does_esmtp = False 993 self.close() 994 return res 995 996if _have_ssl: 997 998 class SMTP_SSL(SMTP): 999 """ This is a subclass derived from SMTP that connects over an SSL 1000 encrypted socket (to use this class you need a socket module that was 1001 compiled with SSL support). If host is not specified, '' (the local 1002 host) is used. If port is omitted, the standard SMTP-over-SSL port 1003 (465) is used. local_hostname and source_address have the same meaning 1004 as they do in the SMTP class. keyfile and certfile are also optional - 1005 they can contain a PEM formatted private key and certificate chain file 1006 for the SSL connection. context also optional, can contain a 1007 SSLContext, and is an alternative to keyfile and certfile; If it is 1008 specified both keyfile and certfile must be None. 1009 1010 """ 1011 1012 default_port = SMTP_SSL_PORT 1013 1014 def __init__(self, host='', port=0, local_hostname=None, 1015 keyfile=None, certfile=None, 1016 timeout=socket._GLOBAL_DEFAULT_TIMEOUT, 1017 source_address=None, context=None): 1018 if context is not None and keyfile is not None: 1019 raise ValueError("context and keyfile arguments are mutually " 1020 "exclusive") 1021 if context is not None and certfile is not None: 1022 raise ValueError("context and certfile arguments are mutually " 1023 "exclusive") 1024 if keyfile is not None or certfile is not None: 1025 import warnings 1026 warnings.warn("keyfile and certfile are deprecated, use a " 1027 "custom context instead", DeprecationWarning, 2) 1028 self.keyfile = keyfile 1029 self.certfile = certfile 1030 if context is None: 1031 context = ssl._create_stdlib_context(certfile=certfile, 1032 keyfile=keyfile) 1033 self.context = context 1034 SMTP.__init__(self, host, port, local_hostname, timeout, 1035 source_address) 1036 1037 def _get_socket(self, host, port, timeout): 1038 if self.debuglevel > 0: 1039 self._print_debug('connect:', (host, port)) 1040 new_socket = super()._get_socket(host, port, timeout) 1041 new_socket = self.context.wrap_socket(new_socket, 1042 server_hostname=self._host) 1043 return new_socket 1044 1045 __all__.append("SMTP_SSL") 1046 1047# 1048# LMTP extension 1049# 1050LMTP_PORT = 2003 1051 1052class LMTP(SMTP): 1053 """LMTP - Local Mail Transfer Protocol 1054 1055 The LMTP protocol, which is very similar to ESMTP, is heavily based 1056 on the standard SMTP client. It's common to use Unix sockets for 1057 LMTP, so our connect() method must support that as well as a regular 1058 host:port server. local_hostname and source_address have the same 1059 meaning as they do in the SMTP class. To specify a Unix socket, 1060 you must use an absolute path as the host, starting with a '/'. 1061 1062 Authentication is supported, using the regular SMTP mechanism. When 1063 using a Unix socket, LMTP generally don't support or require any 1064 authentication, but your mileage might vary.""" 1065 1066 ehlo_msg = "lhlo" 1067 1068 def __init__(self, host='', port=LMTP_PORT, local_hostname=None, 1069 source_address=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT): 1070 """Initialize a new instance.""" 1071 super().__init__(host, port, local_hostname=local_hostname, 1072 source_address=source_address, timeout=timeout) 1073 1074 def connect(self, host='localhost', port=0, source_address=None): 1075 """Connect to the LMTP daemon, on either a Unix or a TCP socket.""" 1076 if host[0] != '/': 1077 return super().connect(host, port, source_address=source_address) 1078 1079 if self.timeout is not None and not self.timeout: 1080 raise ValueError('Non-blocking socket (timeout=0) is not supported') 1081 1082 # Handle Unix-domain sockets. 1083 try: 1084 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 1085 self.sock.settimeout(self.timeout) 1086 self.file = None 1087 self.sock.connect(host) 1088 except OSError: 1089 if self.debuglevel > 0: 1090 self._print_debug('connect fail:', host) 1091 if self.sock: 1092 self.sock.close() 1093 self.sock = None 1094 raise 1095 (code, msg) = self.getreply() 1096 if self.debuglevel > 0: 1097 self._print_debug('connect:', msg) 1098 return (code, msg) 1099 1100 1101# Test the sendmail method, which tests most of the others. 1102# Note: This always sends to localhost. 1103if __name__ == '__main__': 1104 def prompt(prompt): 1105 sys.stdout.write(prompt + ": ") 1106 sys.stdout.flush() 1107 return sys.stdin.readline().strip() 1108 1109 fromaddr = prompt("From") 1110 toaddrs = prompt("To").split(',') 1111 print("Enter message, end with ^D:") 1112 msg = '' 1113 while 1: 1114 line = sys.stdin.readline() 1115 if not line: 1116 break 1117 msg = msg + line 1118 print("Message length is %d" % len(msg)) 1119 1120 server = SMTP('localhost') 1121 server.set_debuglevel(1) 1122 server.sendmail(fromaddr, toaddrs, msg) 1123 server.quit() 1124