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