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