1#!/usr/local/bin/python3.8 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 = 0 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 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 s = cmd 369 else: 370 s = f'{cmd} {args}' 371 if '\r' in s or '\n' in s: 372 s = s.replace('\n', '\\n').replace('\r', '\\r') 373 raise ValueError( 374 f'command and arguments contain prohibited newline characters: {s}' 375 ) 376 self.send(f'{s}{CRLF}') 377 378 def getreply(self): 379 """Get a reply from the server. 380 381 Returns a tuple consisting of: 382 383 - server response code (e.g. '250', or such, if all goes well) 384 Note: returns -1 if it can't read response code. 385 386 - server response string corresponding to response code (multiline 387 responses are converted to a single, multiline string). 388 389 Raises SMTPServerDisconnected if end-of-file is reached. 390 """ 391 resp = [] 392 if self.file is None: 393 self.file = self.sock.makefile('rb') 394 while 1: 395 try: 396 line = self.file.readline(_MAXLINE + 1) 397 except OSError as e: 398 self.close() 399 raise SMTPServerDisconnected("Connection unexpectedly closed: " 400 + str(e)) 401 if not line: 402 self.close() 403 raise SMTPServerDisconnected("Connection unexpectedly closed") 404 if self.debuglevel > 0: 405 self._print_debug('reply:', repr(line)) 406 if len(line) > _MAXLINE: 407 self.close() 408 raise SMTPResponseException(500, "Line too long.") 409 resp.append(line[4:].strip(b' \t\r\n')) 410 code = line[:3] 411 # Check that the error code is syntactically correct. 412 # Don't attempt to read a continuation line if it is broken. 413 try: 414 errcode = int(code) 415 except ValueError: 416 errcode = -1 417 break 418 # Check if multiline response. 419 if line[3:4] != b"-": 420 break 421 422 errmsg = b"\n".join(resp) 423 if self.debuglevel > 0: 424 self._print_debug('reply: retcode (%s); Msg: %a' % (errcode, errmsg)) 425 return errcode, errmsg 426 427 def docmd(self, cmd, args=""): 428 """Send a command, and return its response code.""" 429 self.putcmd(cmd, args) 430 return self.getreply() 431 432 # std smtp commands 433 def helo(self, name=''): 434 """SMTP 'helo' command. 435 Hostname to send for this command defaults to the FQDN of the local 436 host. 437 """ 438 self.putcmd("helo", name or self.local_hostname) 439 (code, msg) = self.getreply() 440 self.helo_resp = msg 441 return (code, msg) 442 443 def ehlo(self, name=''): 444 """ SMTP 'ehlo' command. 445 Hostname to send for this command defaults to the FQDN of the local 446 host. 447 """ 448 self.esmtp_features = {} 449 self.putcmd(self.ehlo_msg, name or self.local_hostname) 450 (code, msg) = self.getreply() 451 # According to RFC1869 some (badly written) 452 # MTA's will disconnect on an ehlo. Toss an exception if 453 # that happens -ddm 454 if code == -1 and len(msg) == 0: 455 self.close() 456 raise SMTPServerDisconnected("Server not connected") 457 self.ehlo_resp = msg 458 if code != 250: 459 return (code, msg) 460 self.does_esmtp = 1 461 #parse the ehlo response -ddm 462 assert isinstance(self.ehlo_resp, bytes), repr(self.ehlo_resp) 463 resp = self.ehlo_resp.decode("latin-1").split('\n') 464 del resp[0] 465 for each in resp: 466 # To be able to communicate with as many SMTP servers as possible, 467 # we have to take the old-style auth advertisement into account, 468 # because: 469 # 1) Else our SMTP feature parser gets confused. 470 # 2) There are some servers that only advertise the auth methods we 471 # support using the old style. 472 auth_match = OLDSTYLE_AUTH.match(each) 473 if auth_match: 474 # This doesn't remove duplicates, but that's no problem 475 self.esmtp_features["auth"] = self.esmtp_features.get("auth", "") \ 476 + " " + auth_match.groups(0)[0] 477 continue 478 479 # RFC 1869 requires a space between ehlo keyword and parameters. 480 # It's actually stricter, in that only spaces are allowed between 481 # parameters, but were not going to check for that here. Note 482 # that the space isn't present if there are no parameters. 483 m = re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*) ?', each) 484 if m: 485 feature = m.group("feature").lower() 486 params = m.string[m.end("feature"):].strip() 487 if feature == "auth": 488 self.esmtp_features[feature] = self.esmtp_features.get(feature, "") \ 489 + " " + params 490 else: 491 self.esmtp_features[feature] = params 492 return (code, msg) 493 494 def has_extn(self, opt): 495 """Does the server support a given SMTP service extension?""" 496 return opt.lower() in self.esmtp_features 497 498 def help(self, args=''): 499 """SMTP 'help' command. 500 Returns help text from server.""" 501 self.putcmd("help", args) 502 return self.getreply()[1] 503 504 def rset(self): 505 """SMTP 'rset' command -- resets session.""" 506 self.command_encoding = 'ascii' 507 return self.docmd("rset") 508 509 def _rset(self): 510 """Internal 'rset' command which ignores any SMTPServerDisconnected error. 511 512 Used internally in the library, since the server disconnected error 513 should appear to the application when the *next* command is issued, if 514 we are doing an internal "safety" reset. 515 """ 516 try: 517 self.rset() 518 except SMTPServerDisconnected: 519 pass 520 521 def noop(self): 522 """SMTP 'noop' command -- doesn't do anything :>""" 523 return self.docmd("noop") 524 525 def mail(self, sender, options=()): 526 """SMTP 'mail' command -- begins mail xfer session. 527 528 This method may raise the following exceptions: 529 530 SMTPNotSupportedError The options parameter includes 'SMTPUTF8' 531 but the SMTPUTF8 extension is not supported by 532 the server. 533 """ 534 optionlist = '' 535 if options and self.does_esmtp: 536 if any(x.lower()=='smtputf8' for x in options): 537 if self.has_extn('smtputf8'): 538 self.command_encoding = 'utf-8' 539 else: 540 raise SMTPNotSupportedError( 541 'SMTPUTF8 not supported by server') 542 optionlist = ' ' + ' '.join(options) 543 self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist)) 544 return self.getreply() 545 546 def rcpt(self, recip, options=()): 547 """SMTP 'rcpt' command -- indicates 1 recipient for this mail.""" 548 optionlist = '' 549 if options and self.does_esmtp: 550 optionlist = ' ' + ' '.join(options) 551 self.putcmd("rcpt", "TO:%s%s" % (quoteaddr(recip), optionlist)) 552 return self.getreply() 553 554 def data(self, msg): 555 """SMTP 'DATA' command -- sends message data to server. 556 557 Automatically quotes lines beginning with a period per rfc821. 558 Raises SMTPDataError if there is an unexpected reply to the 559 DATA command; the return value from this method is the final 560 response code received when the all data is sent. If msg 561 is a string, lone '\\r' and '\\n' characters are converted to 562 '\\r\\n' characters. If msg is bytes, it is transmitted as is. 563 """ 564 self.putcmd("data") 565 (code, repl) = self.getreply() 566 if self.debuglevel > 0: 567 self._print_debug('data:', (code, repl)) 568 if code != 354: 569 raise SMTPDataError(code, repl) 570 else: 571 if isinstance(msg, str): 572 msg = _fix_eols(msg).encode('ascii') 573 q = _quote_periods(msg) 574 if q[-2:] != bCRLF: 575 q = q + bCRLF 576 q = q + b"." + bCRLF 577 self.send(q) 578 (code, msg) = self.getreply() 579 if self.debuglevel > 0: 580 self._print_debug('data:', (code, msg)) 581 return (code, msg) 582 583 def verify(self, address): 584 """SMTP 'verify' command -- checks for address validity.""" 585 self.putcmd("vrfy", _addr_only(address)) 586 return self.getreply() 587 # a.k.a. 588 vrfy = verify 589 590 def expn(self, address): 591 """SMTP 'expn' command -- expands a mailing list.""" 592 self.putcmd("expn", _addr_only(address)) 593 return self.getreply() 594 595 # some useful methods 596 597 def ehlo_or_helo_if_needed(self): 598 """Call self.ehlo() and/or self.helo() if needed. 599 600 If there has been no previous EHLO or HELO command this session, this 601 method tries ESMTP EHLO first. 602 603 This method may raise the following exceptions: 604 605 SMTPHeloError The server didn't reply properly to 606 the helo greeting. 607 """ 608 if self.helo_resp is None and self.ehlo_resp is None: 609 if not (200 <= self.ehlo()[0] <= 299): 610 (code, resp) = self.helo() 611 if not (200 <= code <= 299): 612 raise SMTPHeloError(code, resp) 613 614 def auth(self, mechanism, authobject, *, initial_response_ok=True): 615 """Authentication command - requires response processing. 616 617 'mechanism' specifies which authentication mechanism is to 618 be used - the valid values are those listed in the 'auth' 619 element of 'esmtp_features'. 620 621 'authobject' must be a callable object taking a single argument: 622 623 data = authobject(challenge) 624 625 It will be called to process the server's challenge response; the 626 challenge argument it is passed will be a bytes. It should return 627 an ASCII string that will be base64 encoded and sent to the server. 628 629 Keyword arguments: 630 - initial_response_ok: Allow sending the RFC 4954 initial-response 631 to the AUTH command, if the authentication methods supports it. 632 """ 633 # RFC 4954 allows auth methods to provide an initial response. Not all 634 # methods support it. By definition, if they return something other 635 # than None when challenge is None, then they do. See issue #15014. 636 mechanism = mechanism.upper() 637 initial_response = (authobject() if initial_response_ok else None) 638 if initial_response is not None: 639 response = encode_base64(initial_response.encode('ascii'), eol='') 640 (code, resp) = self.docmd("AUTH", mechanism + " " + response) 641 self._auth_challenge_count = 1 642 else: 643 (code, resp) = self.docmd("AUTH", mechanism) 644 self._auth_challenge_count = 0 645 # If server responds with a challenge, send the response. 646 while code == 334: 647 self._auth_challenge_count += 1 648 challenge = base64.decodebytes(resp) 649 response = encode_base64( 650 authobject(challenge).encode('ascii'), eol='') 651 (code, resp) = self.docmd(response) 652 # If server keeps sending challenges, something is wrong. 653 if self._auth_challenge_count > _MAXCHALLENGE: 654 raise SMTPException( 655 "Server AUTH mechanism infinite loop. Last response: " 656 + repr((code, resp)) 657 ) 658 if code in (235, 503): 659 return (code, resp) 660 raise SMTPAuthenticationError(code, resp) 661 662 def auth_cram_md5(self, challenge=None): 663 """ Authobject to use with CRAM-MD5 authentication. Requires self.user 664 and self.password to be set.""" 665 # CRAM-MD5 does not support initial-response. 666 if challenge is None: 667 return None 668 return self.user + " " + hmac.HMAC( 669 self.password.encode('ascii'), challenge, 'md5').hexdigest() 670 671 def auth_plain(self, challenge=None): 672 """ Authobject to use with PLAIN authentication. Requires self.user and 673 self.password to be set.""" 674 return "\0%s\0%s" % (self.user, self.password) 675 676 def auth_login(self, challenge=None): 677 """ Authobject to use with LOGIN authentication. Requires self.user and 678 self.password to be set.""" 679 if challenge is None or self._auth_challenge_count < 2: 680 return self.user 681 else: 682 return self.password 683 684 def login(self, user, password, *, initial_response_ok=True): 685 """Log in on an SMTP server that requires authentication. 686 687 The arguments are: 688 - user: The user name to authenticate with. 689 - password: The password for the authentication. 690 691 Keyword arguments: 692 - initial_response_ok: Allow sending the RFC 4954 initial-response 693 to the AUTH command, if the authentication methods supports it. 694 695 If there has been no previous EHLO or HELO command this session, this 696 method tries ESMTP EHLO first. 697 698 This method will return normally if the authentication was successful. 699 700 This method may raise the following exceptions: 701 702 SMTPHeloError The server didn't reply properly to 703 the helo greeting. 704 SMTPAuthenticationError The server didn't accept the username/ 705 password combination. 706 SMTPNotSupportedError The AUTH command is not supported by the 707 server. 708 SMTPException No suitable authentication method was 709 found. 710 """ 711 712 self.ehlo_or_helo_if_needed() 713 if not self.has_extn("auth"): 714 raise SMTPNotSupportedError( 715 "SMTP AUTH extension not supported by server.") 716 717 # Authentication methods the server claims to support 718 advertised_authlist = self.esmtp_features["auth"].split() 719 720 # Authentication methods we can handle in our preferred order: 721 preferred_auths = ['CRAM-MD5', 'PLAIN', 'LOGIN'] 722 723 # We try the supported authentications in our preferred order, if 724 # the server supports them. 725 authlist = [auth for auth in preferred_auths 726 if auth in advertised_authlist] 727 if not authlist: 728 raise SMTPException("No suitable authentication method found.") 729 730 # Some servers advertise authentication methods they don't really 731 # support, so if authentication fails, we continue until we've tried 732 # all methods. 733 self.user, self.password = user, password 734 for authmethod in authlist: 735 method_name = 'auth_' + authmethod.lower().replace('-', '_') 736 try: 737 (code, resp) = self.auth( 738 authmethod, getattr(self, method_name), 739 initial_response_ok=initial_response_ok) 740 # 235 == 'Authentication successful' 741 # 503 == 'Error: already authenticated' 742 if code in (235, 503): 743 return (code, resp) 744 except SMTPAuthenticationError as e: 745 last_exception = e 746 747 # We could not login successfully. Return result of last attempt. 748 raise last_exception 749 750 def starttls(self, keyfile=None, certfile=None, context=None): 751 """Puts the connection to the SMTP server into TLS mode. 752 753 If there has been no previous EHLO or HELO command this session, this 754 method tries ESMTP EHLO first. 755 756 If the server supports TLS, this will encrypt the rest of the SMTP 757 session. If you provide the keyfile and certfile parameters, 758 the identity of the SMTP server and client can be checked. This, 759 however, depends on whether the socket module really checks the 760 certificates. 761 762 This method may raise the following exceptions: 763 764 SMTPHeloError The server didn't reply properly to 765 the helo greeting. 766 """ 767 self.ehlo_or_helo_if_needed() 768 if not self.has_extn("starttls"): 769 raise SMTPNotSupportedError( 770 "STARTTLS extension not supported by server.") 771 (resp, reply) = self.docmd("STARTTLS") 772 if resp == 220: 773 if not _have_ssl: 774 raise RuntimeError("No SSL support included in this Python") 775 if context is not None and keyfile is not None: 776 raise ValueError("context and keyfile arguments are mutually " 777 "exclusive") 778 if context is not None and certfile is not None: 779 raise ValueError("context and certfile arguments are mutually " 780 "exclusive") 781 if keyfile is not None or certfile is not None: 782 import warnings 783 warnings.warn("keyfile and certfile are deprecated, use a " 784 "custom context instead", DeprecationWarning, 2) 785 if context is None: 786 context = ssl._create_stdlib_context(certfile=certfile, 787 keyfile=keyfile) 788 self.sock = context.wrap_socket(self.sock, 789 server_hostname=self._host) 790 self.file = None 791 # RFC 3207: 792 # The client MUST discard any knowledge obtained from 793 # the server, such as the list of SMTP service extensions, 794 # which was not obtained from the TLS negotiation itself. 795 self.helo_resp = None 796 self.ehlo_resp = None 797 self.esmtp_features = {} 798 self.does_esmtp = 0 799 else: 800 # RFC 3207: 801 # 501 Syntax error (no parameters allowed) 802 # 454 TLS not available due to temporary reason 803 raise SMTPResponseException(resp, reply) 804 return (resp, reply) 805 806 def sendmail(self, from_addr, to_addrs, msg, mail_options=(), 807 rcpt_options=()): 808 """This command performs an entire mail transaction. 809 810 The arguments are: 811 - from_addr : The address sending this mail. 812 - to_addrs : A list of addresses to send this mail to. A bare 813 string will be treated as a list with 1 address. 814 - msg : The message to send. 815 - mail_options : List of ESMTP options (such as 8bitmime) for the 816 mail command. 817 - rcpt_options : List of ESMTP options (such as DSN commands) for 818 all the rcpt commands. 819 820 msg may be a string containing characters in the ASCII range, or a byte 821 string. A string is encoded to bytes using the ascii codec, and lone 822 \\r and \\n characters are converted to \\r\\n characters. 823 824 If there has been no previous EHLO or HELO command this session, this 825 method tries ESMTP EHLO first. If the server does ESMTP, message size 826 and each of the specified options will be passed to it. If EHLO 827 fails, HELO will be tried and ESMTP options suppressed. 828 829 This method will return normally if the mail is accepted for at least 830 one recipient. It returns a dictionary, with one entry for each 831 recipient that was refused. Each entry contains a tuple of the SMTP 832 error code and the accompanying error message sent by the server. 833 834 This method may raise the following exceptions: 835 836 SMTPHeloError The server didn't reply properly to 837 the helo greeting. 838 SMTPRecipientsRefused The server rejected ALL recipients 839 (no mail was sent). 840 SMTPSenderRefused The server didn't accept the from_addr. 841 SMTPDataError The server replied with an unexpected 842 error code (other than a refusal of 843 a recipient). 844 SMTPNotSupportedError The mail_options parameter includes 'SMTPUTF8' 845 but the SMTPUTF8 extension is not supported by 846 the server. 847 848 Note: the connection will be open even after an exception is raised. 849 850 Example: 851 852 >>> import smtplib 853 >>> s=smtplib.SMTP("localhost") 854 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"] 855 >>> msg = '''\\ 856 ... From: Me@my.org 857 ... Subject: testin'... 858 ... 859 ... This is a test ''' 860 >>> s.sendmail("me@my.org",tolist,msg) 861 { "three@three.org" : ( 550 ,"User unknown" ) } 862 >>> s.quit() 863 864 In the above example, the message was accepted for delivery to three 865 of the four addresses, and one was rejected, with the error code 866 550. If all addresses are accepted, then the method will return an 867 empty dictionary. 868 869 """ 870 self.ehlo_or_helo_if_needed() 871 esmtp_opts = [] 872 if isinstance(msg, str): 873 msg = _fix_eols(msg).encode('ascii') 874 if self.does_esmtp: 875 if self.has_extn('size'): 876 esmtp_opts.append("size=%d" % len(msg)) 877 for option in mail_options: 878 esmtp_opts.append(option) 879 (code, resp) = self.mail(from_addr, esmtp_opts) 880 if code != 250: 881 if code == 421: 882 self.close() 883 else: 884 self._rset() 885 raise SMTPSenderRefused(code, resp, from_addr) 886 senderrs = {} 887 if isinstance(to_addrs, str): 888 to_addrs = [to_addrs] 889 for each in to_addrs: 890 (code, resp) = self.rcpt(each, rcpt_options) 891 if (code != 250) and (code != 251): 892 senderrs[each] = (code, resp) 893 if code == 421: 894 self.close() 895 raise SMTPRecipientsRefused(senderrs) 896 if len(senderrs) == len(to_addrs): 897 # the server refused all our recipients 898 self._rset() 899 raise SMTPRecipientsRefused(senderrs) 900 (code, resp) = self.data(msg) 901 if code != 250: 902 if code == 421: 903 self.close() 904 else: 905 self._rset() 906 raise SMTPDataError(code, resp) 907 #if we got here then somebody got our mail 908 return senderrs 909 910 def send_message(self, msg, from_addr=None, to_addrs=None, 911 mail_options=(), rcpt_options=()): 912 """Converts message to a bytestring and passes it to sendmail. 913 914 The arguments are as for sendmail, except that msg is an 915 email.message.Message object. If from_addr is None or to_addrs is 916 None, these arguments are taken from the headers of the Message as 917 described in RFC 2822 (a ValueError is raised if there is more than 918 one set of 'Resent-' headers). Regardless of the values of from_addr and 919 to_addr, any Bcc field (or Resent-Bcc field, when the Message is a 920 resent) of the Message object won't be transmitted. The Message 921 object is then serialized using email.generator.BytesGenerator and 922 sendmail is called to transmit the message. If the sender or any of 923 the recipient addresses contain non-ASCII and the server advertises the 924 SMTPUTF8 capability, the policy is cloned with utf8 set to True for the 925 serialization, and SMTPUTF8 and BODY=8BITMIME are asserted on the send. 926 If the server does not support SMTPUTF8, an SMTPNotSupported error is 927 raised. Otherwise the generator is called without modifying the 928 policy. 929 930 """ 931 # 'Resent-Date' is a mandatory field if the Message is resent (RFC 2822 932 # Section 3.6.6). In such a case, we use the 'Resent-*' fields. However, 933 # if there is more than one 'Resent-' block there's no way to 934 # unambiguously determine which one is the most recent in all cases, 935 # so rather than guess we raise a ValueError in that case. 936 # 937 # TODO implement heuristics to guess the correct Resent-* block with an 938 # option allowing the user to enable the heuristics. (It should be 939 # possible to guess correctly almost all of the time.) 940 941 self.ehlo_or_helo_if_needed() 942 resent = msg.get_all('Resent-Date') 943 if resent is None: 944 header_prefix = '' 945 elif len(resent) == 1: 946 header_prefix = 'Resent-' 947 else: 948 raise ValueError("message has more than one 'Resent-' header block") 949 if from_addr is None: 950 # Prefer the sender field per RFC 2822:3.6.2. 951 from_addr = (msg[header_prefix + 'Sender'] 952 if (header_prefix + 'Sender') in msg 953 else msg[header_prefix + 'From']) 954 from_addr = email.utils.getaddresses([from_addr])[0][1] 955 if to_addrs is None: 956 addr_fields = [f for f in (msg[header_prefix + 'To'], 957 msg[header_prefix + 'Bcc'], 958 msg[header_prefix + 'Cc']) 959 if f is not None] 960 to_addrs = [a[1] for a in email.utils.getaddresses(addr_fields)] 961 # Make a local copy so we can delete the bcc headers. 962 msg_copy = copy.copy(msg) 963 del msg_copy['Bcc'] 964 del msg_copy['Resent-Bcc'] 965 international = False 966 try: 967 ''.join([from_addr, *to_addrs]).encode('ascii') 968 except UnicodeEncodeError: 969 if not self.has_extn('smtputf8'): 970 raise SMTPNotSupportedError( 971 "One or more source or delivery addresses require" 972 " internationalized email support, but the server" 973 " does not advertise the required SMTPUTF8 capability") 974 international = True 975 with io.BytesIO() as bytesmsg: 976 if international: 977 g = email.generator.BytesGenerator( 978 bytesmsg, policy=msg.policy.clone(utf8=True)) 979 mail_options = (*mail_options, 'SMTPUTF8', 'BODY=8BITMIME') 980 else: 981 g = email.generator.BytesGenerator(bytesmsg) 982 g.flatten(msg_copy, linesep='\r\n') 983 flatmsg = bytesmsg.getvalue() 984 return self.sendmail(from_addr, to_addrs, flatmsg, mail_options, 985 rcpt_options) 986 987 def close(self): 988 """Close the connection to the SMTP server.""" 989 try: 990 file = self.file 991 self.file = None 992 if file: 993 file.close() 994 finally: 995 sock = self.sock 996 self.sock = None 997 if sock: 998 sock.close() 999 1000 def quit(self): 1001 """Terminate the SMTP session.""" 1002 res = self.docmd("quit") 1003 # A new EHLO is required after reconnecting with connect() 1004 self.ehlo_resp = self.helo_resp = None 1005 self.esmtp_features = {} 1006 self.does_esmtp = False 1007 self.close() 1008 return res 1009 1010if _have_ssl: 1011 1012 class SMTP_SSL(SMTP): 1013 """ This is a subclass derived from SMTP that connects over an SSL 1014 encrypted socket (to use this class you need a socket module that was 1015 compiled with SSL support). If host is not specified, '' (the local 1016 host) is used. If port is omitted, the standard SMTP-over-SSL port 1017 (465) is used. local_hostname and source_address have the same meaning 1018 as they do in the SMTP class. keyfile and certfile are also optional - 1019 they can contain a PEM formatted private key and certificate chain file 1020 for the SSL connection. context also optional, can contain a 1021 SSLContext, and is an alternative to keyfile and certfile; If it is 1022 specified both keyfile and certfile must be None. 1023 1024 """ 1025 1026 default_port = SMTP_SSL_PORT 1027 1028 def __init__(self, host='', port=0, local_hostname=None, 1029 keyfile=None, certfile=None, 1030 timeout=socket._GLOBAL_DEFAULT_TIMEOUT, 1031 source_address=None, context=None): 1032 if context is not None and keyfile is not None: 1033 raise ValueError("context and keyfile arguments are mutually " 1034 "exclusive") 1035 if context is not None and certfile is not None: 1036 raise ValueError("context and certfile arguments are mutually " 1037 "exclusive") 1038 if keyfile is not None or certfile is not None: 1039 import warnings 1040 warnings.warn("keyfile and certfile are deprecated, use a " 1041 "custom context instead", DeprecationWarning, 2) 1042 self.keyfile = keyfile 1043 self.certfile = certfile 1044 if context is None: 1045 context = ssl._create_stdlib_context(certfile=certfile, 1046 keyfile=keyfile) 1047 self.context = context 1048 SMTP.__init__(self, host, port, local_hostname, timeout, 1049 source_address) 1050 1051 def _get_socket(self, host, port, timeout): 1052 if self.debuglevel > 0: 1053 self._print_debug('connect:', (host, port)) 1054 new_socket = socket.create_connection((host, port), timeout, 1055 self.source_address) 1056 new_socket = self.context.wrap_socket(new_socket, 1057 server_hostname=self._host) 1058 return new_socket 1059 1060 __all__.append("SMTP_SSL") 1061 1062# 1063# LMTP extension 1064# 1065LMTP_PORT = 2003 1066 1067class LMTP(SMTP): 1068 """LMTP - Local Mail Transfer Protocol 1069 1070 The LMTP protocol, which is very similar to ESMTP, is heavily based 1071 on the standard SMTP client. It's common to use Unix sockets for 1072 LMTP, so our connect() method must support that as well as a regular 1073 host:port server. local_hostname and source_address have the same 1074 meaning as they do in the SMTP class. To specify a Unix socket, 1075 you must use an absolute path as the host, starting with a '/'. 1076 1077 Authentication is supported, using the regular SMTP mechanism. When 1078 using a Unix socket, LMTP generally don't support or require any 1079 authentication, but your mileage might vary.""" 1080 1081 ehlo_msg = "lhlo" 1082 1083 def __init__(self, host='', port=LMTP_PORT, local_hostname=None, 1084 source_address=None): 1085 """Initialize a new instance.""" 1086 SMTP.__init__(self, host, port, local_hostname=local_hostname, 1087 source_address=source_address) 1088 1089 def connect(self, host='localhost', port=0, source_address=None): 1090 """Connect to the LMTP daemon, on either a Unix or a TCP socket.""" 1091 if host[0] != '/': 1092 return SMTP.connect(self, host, port, source_address=source_address) 1093 1094 # Handle Unix-domain sockets. 1095 try: 1096 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 1097 self.file = None 1098 self.sock.connect(host) 1099 except OSError: 1100 if self.debuglevel > 0: 1101 self._print_debug('connect fail:', host) 1102 if self.sock: 1103 self.sock.close() 1104 self.sock = None 1105 raise 1106 (code, msg) = self.getreply() 1107 if self.debuglevel > 0: 1108 self._print_debug('connect:', msg) 1109 return (code, msg) 1110 1111 1112# Test the sendmail method, which tests most of the others. 1113# Note: This always sends to localhost. 1114if __name__ == '__main__': 1115 def prompt(prompt): 1116 sys.stdout.write(prompt + ": ") 1117 sys.stdout.flush() 1118 return sys.stdin.readline().strip() 1119 1120 fromaddr = prompt("From") 1121 toaddrs = prompt("To").split(',') 1122 print("Enter message, end with ^D:") 1123 msg = '' 1124 while 1: 1125 line = sys.stdin.readline() 1126 if not line: 1127 break 1128 msg = msg + line 1129 print("Message length is %d" % len(msg)) 1130 1131 server = SMTP('localhost') 1132 server.set_debuglevel(1) 1133 server.sendmail(fromaddr, toaddrs, msg) 1134 server.quit() 1135