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, keyfile=None, certfile=None, 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 keyfile and certfile parameters, 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 not None and keyfile is not None: 778 raise ValueError("context and keyfile arguments are mutually " 779 "exclusive") 780 if context is not None and certfile is not None: 781 raise ValueError("context and certfile arguments are mutually " 782 "exclusive") 783 if keyfile is not None or certfile is not None: 784 import warnings 785 warnings.warn("keyfile and certfile are deprecated, use a " 786 "custom context instead", DeprecationWarning, 2) 787 if context is None: 788 context = ssl._create_stdlib_context(certfile=certfile, 789 keyfile=keyfile) 790 self.sock = context.wrap_socket(self.sock, 791 server_hostname=self._host) 792 self.file = None 793 # RFC 3207: 794 # The client MUST discard any knowledge obtained from 795 # the server, such as the list of SMTP service extensions, 796 # which was not obtained from the TLS negotiation itself. 797 self.helo_resp = None 798 self.ehlo_resp = None 799 self.esmtp_features = {} 800 self.does_esmtp = False 801 else: 802 # RFC 3207: 803 # 501 Syntax error (no parameters allowed) 804 # 454 TLS not available due to temporary reason 805 raise SMTPResponseException(resp, reply) 806 return (resp, reply) 807 808 def sendmail(self, from_addr, to_addrs, msg, mail_options=(), 809 rcpt_options=()): 810 """This command performs an entire mail transaction. 811 812 The arguments are: 813 - from_addr : The address sending this mail. 814 - to_addrs : A list of addresses to send this mail to. A bare 815 string will be treated as a list with 1 address. 816 - msg : The message to send. 817 - mail_options : List of ESMTP options (such as 8bitmime) for the 818 mail command. 819 - rcpt_options : List of ESMTP options (such as DSN commands) for 820 all the rcpt commands. 821 822 msg may be a string containing characters in the ASCII range, or a byte 823 string. A string is encoded to bytes using the ascii codec, and lone 824 \\r and \\n characters are converted to \\r\\n characters. 825 826 If there has been no previous EHLO or HELO command this session, this 827 method tries ESMTP EHLO first. If the server does ESMTP, message size 828 and each of the specified options will be passed to it. If EHLO 829 fails, HELO will be tried and ESMTP options suppressed. 830 831 This method will return normally if the mail is accepted for at least 832 one recipient. It returns a dictionary, with one entry for each 833 recipient that was refused. Each entry contains a tuple of the SMTP 834 error code and the accompanying error message sent by the server. 835 836 This method may raise the following exceptions: 837 838 SMTPHeloError The server didn't reply properly to 839 the helo greeting. 840 SMTPRecipientsRefused The server rejected ALL recipients 841 (no mail was sent). 842 SMTPSenderRefused The server didn't accept the from_addr. 843 SMTPDataError The server replied with an unexpected 844 error code (other than a refusal of 845 a recipient). 846 SMTPNotSupportedError The mail_options parameter includes 'SMTPUTF8' 847 but the SMTPUTF8 extension is not supported by 848 the server. 849 850 Note: the connection will be open even after an exception is raised. 851 852 Example: 853 854 >>> import smtplib 855 >>> s=smtplib.SMTP("localhost") 856 >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"] 857 >>> msg = '''\\ 858 ... From: Me@my.org 859 ... Subject: testin'... 860 ... 861 ... This is a test ''' 862 >>> s.sendmail("me@my.org",tolist,msg) 863 { "three@three.org" : ( 550 ,"User unknown" ) } 864 >>> s.quit() 865 866 In the above example, the message was accepted for delivery to three 867 of the four addresses, and one was rejected, with the error code 868 550. If all addresses are accepted, then the method will return an 869 empty dictionary. 870 871 """ 872 self.ehlo_or_helo_if_needed() 873 esmtp_opts = [] 874 if isinstance(msg, str): 875 msg = _fix_eols(msg).encode('ascii') 876 if self.does_esmtp: 877 if self.has_extn('size'): 878 esmtp_opts.append("size=%d" % len(msg)) 879 for option in mail_options: 880 esmtp_opts.append(option) 881 (code, resp) = self.mail(from_addr, esmtp_opts) 882 if code != 250: 883 if code == 421: 884 self.close() 885 else: 886 self._rset() 887 raise SMTPSenderRefused(code, resp, from_addr) 888 senderrs = {} 889 if isinstance(to_addrs, str): 890 to_addrs = [to_addrs] 891 for each in to_addrs: 892 (code, resp) = self.rcpt(each, rcpt_options) 893 if (code != 250) and (code != 251): 894 senderrs[each] = (code, resp) 895 if code == 421: 896 self.close() 897 raise SMTPRecipientsRefused(senderrs) 898 if len(senderrs) == len(to_addrs): 899 # the server refused all our recipients 900 self._rset() 901 raise SMTPRecipientsRefused(senderrs) 902 (code, resp) = self.data(msg) 903 if code != 250: 904 if code == 421: 905 self.close() 906 else: 907 self._rset() 908 raise SMTPDataError(code, resp) 909 #if we got here then somebody got our mail 910 return senderrs 911 912 def send_message(self, msg, from_addr=None, to_addrs=None, 913 mail_options=(), rcpt_options=()): 914 """Converts message to a bytestring and passes it to sendmail. 915 916 The arguments are as for sendmail, except that msg is an 917 email.message.Message object. If from_addr is None or to_addrs is 918 None, these arguments are taken from the headers of the Message as 919 described in RFC 2822 (a ValueError is raised if there is more than 920 one set of 'Resent-' headers). Regardless of the values of from_addr and 921 to_addr, any Bcc field (or Resent-Bcc field, when the Message is a 922 resent) of the Message object won't be transmitted. The Message 923 object is then serialized using email.generator.BytesGenerator and 924 sendmail is called to transmit the message. If the sender or any of 925 the recipient addresses contain non-ASCII and the server advertises the 926 SMTPUTF8 capability, the policy is cloned with utf8 set to True for the 927 serialization, and SMTPUTF8 and BODY=8BITMIME are asserted on the send. 928 If the server does not support SMTPUTF8, an SMTPNotSupported error is 929 raised. Otherwise the generator is called without modifying the 930 policy. 931 932 """ 933 # 'Resent-Date' is a mandatory field if the Message is resent (RFC 2822 934 # Section 3.6.6). In such a case, we use the 'Resent-*' fields. However, 935 # if there is more than one 'Resent-' block there's no way to 936 # unambiguously determine which one is the most recent in all cases, 937 # so rather than guess we raise a ValueError in that case. 938 # 939 # TODO implement heuristics to guess the correct Resent-* block with an 940 # option allowing the user to enable the heuristics. (It should be 941 # possible to guess correctly almost all of the time.) 942 943 self.ehlo_or_helo_if_needed() 944 resent = msg.get_all('Resent-Date') 945 if resent is None: 946 header_prefix = '' 947 elif len(resent) == 1: 948 header_prefix = 'Resent-' 949 else: 950 raise ValueError("message has more than one 'Resent-' header block") 951 if from_addr is None: 952 # Prefer the sender field per RFC 2822:3.6.2. 953 from_addr = (msg[header_prefix + 'Sender'] 954 if (header_prefix + 'Sender') in msg 955 else msg[header_prefix + 'From']) 956 from_addr = email.utils.getaddresses([from_addr])[0][1] 957 if to_addrs is None: 958 addr_fields = [f for f in (msg[header_prefix + 'To'], 959 msg[header_prefix + 'Bcc'], 960 msg[header_prefix + 'Cc']) 961 if f is not None] 962 to_addrs = [a[1] for a in email.utils.getaddresses(addr_fields)] 963 # Make a local copy so we can delete the bcc headers. 964 msg_copy = copy.copy(msg) 965 del msg_copy['Bcc'] 966 del msg_copy['Resent-Bcc'] 967 international = False 968 try: 969 ''.join([from_addr, *to_addrs]).encode('ascii') 970 except UnicodeEncodeError: 971 if not self.has_extn('smtputf8'): 972 raise SMTPNotSupportedError( 973 "One or more source or delivery addresses require" 974 " internationalized email support, but the server" 975 " does not advertise the required SMTPUTF8 capability") 976 international = True 977 with io.BytesIO() as bytesmsg: 978 if international: 979 g = email.generator.BytesGenerator( 980 bytesmsg, policy=msg.policy.clone(utf8=True)) 981 mail_options = (*mail_options, 'SMTPUTF8', 'BODY=8BITMIME') 982 else: 983 g = email.generator.BytesGenerator(bytesmsg) 984 g.flatten(msg_copy, linesep='\r\n') 985 flatmsg = bytesmsg.getvalue() 986 return self.sendmail(from_addr, to_addrs, flatmsg, mail_options, 987 rcpt_options) 988 989 def close(self): 990 """Close the connection to the SMTP server.""" 991 try: 992 file = self.file 993 self.file = None 994 if file: 995 file.close() 996 finally: 997 sock = self.sock 998 self.sock = None 999 if sock: 1000 sock.close() 1001 1002 def quit(self): 1003 """Terminate the SMTP session.""" 1004 res = self.docmd("quit") 1005 # A new EHLO is required after reconnecting with connect() 1006 self.ehlo_resp = self.helo_resp = None 1007 self.esmtp_features = {} 1008 self.does_esmtp = False 1009 self.close() 1010 return res 1011 1012if _have_ssl: 1013 1014 class SMTP_SSL(SMTP): 1015 """ This is a subclass derived from SMTP that connects over an SSL 1016 encrypted socket (to use this class you need a socket module that was 1017 compiled with SSL support). If host is not specified, '' (the local 1018 host) is used. If port is omitted, the standard SMTP-over-SSL port 1019 (465) is used. local_hostname and source_address have the same meaning 1020 as they do in the SMTP class. keyfile and certfile are also optional - 1021 they can contain a PEM formatted private key and certificate chain file 1022 for the SSL connection. context also optional, can contain a 1023 SSLContext, and is an alternative to keyfile and certfile; If it is 1024 specified both keyfile and certfile must be None. 1025 1026 """ 1027 1028 default_port = SMTP_SSL_PORT 1029 1030 def __init__(self, host='', port=0, local_hostname=None, 1031 keyfile=None, certfile=None, 1032 timeout=socket._GLOBAL_DEFAULT_TIMEOUT, 1033 source_address=None, context=None): 1034 if context is not None and keyfile is not None: 1035 raise ValueError("context and keyfile arguments are mutually " 1036 "exclusive") 1037 if context is not None and certfile is not None: 1038 raise ValueError("context and certfile arguments are mutually " 1039 "exclusive") 1040 if keyfile is not None or certfile is not None: 1041 import warnings 1042 warnings.warn("keyfile and certfile are deprecated, use a " 1043 "custom context instead", DeprecationWarning, 2) 1044 self.keyfile = keyfile 1045 self.certfile = certfile 1046 if context is None: 1047 context = ssl._create_stdlib_context(certfile=certfile, 1048 keyfile=keyfile) 1049 self.context = context 1050 SMTP.__init__(self, host, port, local_hostname, timeout, 1051 source_address) 1052 1053 def _get_socket(self, host, port, timeout): 1054 if self.debuglevel > 0: 1055 self._print_debug('connect:', (host, port)) 1056 new_socket = super()._get_socket(host, port, timeout) 1057 new_socket = self.context.wrap_socket(new_socket, 1058 server_hostname=self._host) 1059 return new_socket 1060 1061 __all__.append("SMTP_SSL") 1062 1063# 1064# LMTP extension 1065# 1066LMTP_PORT = 2003 1067 1068class LMTP(SMTP): 1069 """LMTP - Local Mail Transfer Protocol 1070 1071 The LMTP protocol, which is very similar to ESMTP, is heavily based 1072 on the standard SMTP client. It's common to use Unix sockets for 1073 LMTP, so our connect() method must support that as well as a regular 1074 host:port server. local_hostname and source_address have the same 1075 meaning as they do in the SMTP class. To specify a Unix socket, 1076 you must use an absolute path as the host, starting with a '/'. 1077 1078 Authentication is supported, using the regular SMTP mechanism. When 1079 using a Unix socket, LMTP generally don't support or require any 1080 authentication, but your mileage might vary.""" 1081 1082 ehlo_msg = "lhlo" 1083 1084 def __init__(self, host='', port=LMTP_PORT, local_hostname=None, 1085 source_address=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT): 1086 """Initialize a new instance.""" 1087 super().__init__(host, port, local_hostname=local_hostname, 1088 source_address=source_address, timeout=timeout) 1089 1090 def connect(self, host='localhost', port=0, source_address=None): 1091 """Connect to the LMTP daemon, on either a Unix or a TCP socket.""" 1092 if host[0] != '/': 1093 return super().connect(host, port, source_address=source_address) 1094 1095 if self.timeout is not None and not self.timeout: 1096 raise ValueError('Non-blocking socket (timeout=0) is not supported') 1097 1098 # Handle Unix-domain sockets. 1099 try: 1100 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 1101 if self.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: 1102 self.sock.settimeout(self.timeout) 1103 self.file = None 1104 self.sock.connect(host) 1105 except OSError: 1106 if self.debuglevel > 0: 1107 self._print_debug('connect fail:', host) 1108 if self.sock: 1109 self.sock.close() 1110 self.sock = None 1111 raise 1112 (code, msg) = self.getreply() 1113 if self.debuglevel > 0: 1114 self._print_debug('connect:', msg) 1115 return (code, msg) 1116 1117 1118# Test the sendmail method, which tests most of the others. 1119# Note: This always sends to localhost. 1120if __name__ == '__main__': 1121 def prompt(prompt): 1122 sys.stdout.write(prompt + ": ") 1123 sys.stdout.flush() 1124 return sys.stdin.readline().strip() 1125 1126 fromaddr = prompt("From") 1127 toaddrs = prompt("To").split(',') 1128 print("Enter message, end with ^D:") 1129 msg = '' 1130 while 1: 1131 line = sys.stdin.readline() 1132 if not line: 1133 break 1134 msg = msg + line 1135 print("Message length is %d" % len(msg)) 1136 1137 server = SMTP('localhost') 1138 server.set_debuglevel(1) 1139 server.sendmail(fromaddr, toaddrs, msg) 1140 server.quit() 1141